home *** CD-ROM | disk | FTP | other *** search
/ Freelog 115 / FreelogNo115-MaiJuin2013.iso / Internet / AvantBrowser / asetup.exe / _data / webkit / resources.pak / Unnamed File 000018.txt < prev    next >
Text File  |  2013-04-03  |  331KB  |  10,143 lines

  1. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4.  
  5. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  6. // Use of this source code is governed by a BSD-style license that can be
  7. // found in the LICENSE file.
  8.  
  9. /**
  10.  * Sets the width (in pixels) on a DOM node.
  11.  * @param {!HtmlNode} node The node whose width is being set.
  12.  * @param {number} widthPx The width in pixels.
  13.  */
  14. function setNodeWidth(node, widthPx) {
  15.   node.style.width = widthPx.toFixed(0) + 'px';
  16. }
  17.  
  18. /**
  19.  * Sets the height (in pixels) on a DOM node.
  20.  * @param {!HtmlNode} node The node whose height is being set.
  21.  * @param {number} heightPx The height in pixels.
  22.  */
  23. function setNodeHeight(node, heightPx) {
  24.   node.style.height = heightPx.toFixed(0) + 'px';
  25. }
  26.  
  27. /**
  28.  * Sets the position and size of a DOM node (in pixels).
  29.  * @param {!HtmlNode} node The node being positioned.
  30.  * @param {number} leftPx The left position in pixels.
  31.  * @param {number} topPx The top position in pixels.
  32.  * @param {number} widthPx The width in pixels.
  33.  * @param {number} heightPx The height in pixels.
  34.  */
  35. function setNodePosition(node, leftPx, topPx, widthPx, heightPx) {
  36.   node.style.left = leftPx.toFixed(0) + 'px';
  37.   node.style.top = topPx.toFixed(0) + 'px';
  38.   setNodeWidth(node, widthPx);
  39.   setNodeHeight(node, heightPx);
  40. }
  41.  
  42. /**
  43.  * Sets the visibility for a DOM node.
  44.  * @param {!HtmlNode} node The node being positioned.
  45.  * @param {boolean} isVisible Whether to show the node or not.
  46.  */
  47. function setNodeDisplay(node, isVisible) {
  48.   node.style.display = isVisible ? '' : 'none';
  49. }
  50.  
  51. /**
  52.  * Adds a node to |parentNode|, of type |tagName|.
  53.  * @param {!HtmlNode} parentNode The node that will be the parent of the new
  54.  *     element.
  55.  * @param {string} tagName the tag name of the new element.
  56.  * @return {!HtmlElement} The newly created element.
  57.  */
  58. function addNode(parentNode, tagName) {
  59.   var elem = parentNode.ownerDocument.createElement(tagName);
  60.   parentNode.appendChild(elem);
  61.   return elem;
  62. }
  63.  
  64. /**
  65.  * Adds |text| to node |parentNode|.
  66.  * @param {!HtmlNode} parentNode The node to add text to.
  67.  * @param {string} text The text to be added.
  68.  * @return {!Object} The newly created text node.
  69.  */
  70. function addTextNode(parentNode, text) {
  71.   var textNode = parentNode.ownerDocument.createTextNode(text);
  72.   parentNode.appendChild(textNode);
  73.   return textNode;
  74. }
  75.  
  76. /**
  77.  * Adds a node to |parentNode|, of type |tagName|.  Then adds
  78.  * |text| to the new node.
  79.  * @param {!HtmlNode} parentNode The node that will be the parent of the new
  80.  *     element.
  81.  * @param {string} tagName the tag name of the new element.
  82.  * @param {string} text The text to be added.
  83.  * @return {!HtmlElement} The newly created element.
  84.  */
  85. function addNodeWithText(parentNode, tagName, text) {
  86.   var elem = parentNode.ownerDocument.createElement(tagName);
  87.   parentNode.appendChild(elem);
  88.   addTextNode(elem, text);
  89.   return elem;
  90. }
  91.  
  92. /**
  93.  * Returns the key such that map[key] == value, or the string '?' if
  94.  * there is no such key.
  95.  * @param {!Object} map The object being used as a lookup table.
  96.  * @param {Object} value The value to be found in |map|.
  97.  * @return {string} The key for |value|, or '?' if there is no such key.
  98.  */
  99. function getKeyWithValue(map, value) {
  100.   for (var key in map) {
  101.     if (map[key] == value)
  102.       return key;
  103.   }
  104.   return '?';
  105. }
  106.  
  107. /**
  108.  * Returns a new map with the keys and values of the input map inverted.
  109.  * @param {!Object} map The object to have its keys and values reversed.  Not
  110.  *     modified by the function call.
  111.  * @return {Object} The new map with the reversed keys and values.
  112.  */
  113. function makeInverseMap(map) {
  114.   var reversed = {};
  115.   for (var key in map)
  116.     reversed[map[key]] = key;
  117.   return reversed;
  118. }
  119.  
  120. /**
  121.  * Looks up |key| in |map|, and returns the resulting entry, if there is one.
  122.  * Otherwise, returns |key|.  Intended primarily for use with incomplete
  123.  * tables, and for reasonable behavior with system enumerations that may be
  124.  * extended in the future.
  125.  * @param {!Object} map The table in which |key| is looked up.
  126.  * @param {string} key The key to look up.
  127.  * @return {Object|string} map[key], if it exists, or |key| if it doesn't.
  128.  */
  129. function tryGetValueWithKey(map, key) {
  130.   if (key in map)
  131.     return map[key];
  132.   return key;
  133. }
  134.  
  135. /**
  136.  * Builds a string by repeating |str| |count| times.
  137.  * @param {string} str The string to be repeated.
  138.  * @param {number} count The number of times to repeat |str|.
  139.  * @return {string} The constructed string
  140.  */
  141. function makeRepeatedString(str, count) {
  142.   var out = [];
  143.   for (var i = 0; i < count; ++i)
  144.     out.push(str);
  145.   return out.join('');
  146. }
  147.  
  148. /**
  149.  * Clones a basic POD object.  Only a new top level object will be cloned.  It
  150.  * will continue to reference the same values as the original object.
  151.  * @param {Object} object The object to be cloned.
  152.  * @return {Object} A copy of |object|.
  153.  */
  154. function shallowCloneObject(object) {
  155.   if (!(object instanceof Object))
  156.     return object;
  157.   var copy = {};
  158.   for (var key in object) {
  159.     copy[key] = object[key];
  160.   }
  161.   return copy;
  162. }
  163.  
  164. /**
  165.  * Helper to make sure singleton classes are not instantiated more than once.
  166.  * @param {Function} ctor The constructor function being checked.
  167.  */
  168. function assertFirstConstructorCall(ctor) {
  169.   // This is the variable which is set by cr.addSingletonGetter().
  170.   if (ctor.hasCreateFirstInstance_) {
  171.     throw Error('The class ' + ctor.name + ' is a singleton, and should ' +
  172.                 'only be accessed using ' + ctor.name + '.getInstance().');
  173.   }
  174.   ctor.hasCreateFirstInstance_ = true;
  175. }
  176.  
  177. function hasTouchScreen() {
  178.   return 'ontouchstart' in window;
  179. }
  180.  
  181. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  182. // Use of this source code is governed by a BSD-style license that can be
  183. // found in the LICENSE file.
  184.  
  185. /**
  186.  * TablePrinter is a helper to format a table as ASCII art or an HTML table.
  187.  *
  188.  * Usage: call addRow() and addCell() repeatedly to specify the data.
  189.  *
  190.  * addHeaderCell() can optionally be called to specify header cells for a
  191.  * single header row.  The header row appears at the top of an HTML formatted
  192.  * table, and uses thead and th tags.  In ascii tables, the header is separated
  193.  * from the table body by a partial row of dashes.
  194.  *
  195.  * setTitle() can optionally be used to set a title that is displayed before
  196.  * the header row.  In HTML tables, it uses the title class and in ascii tables
  197.  * it's between two rows of dashes.
  198.  *
  199.  * Once all the fields have been input, call toText() to format it as text or
  200.  * toHTML() to format it as HTML.
  201.  */
  202. var TablePrinter = (function() {
  203.   'use strict';
  204.  
  205.   /**
  206.    * @constructor
  207.    */
  208.   function TablePrinter() {
  209.     this.rows_ = [];
  210.     this.hasHeaderRow_ = false;
  211.     this.title_ = null;
  212.     // Number of cells automatically added at the start of new rows.
  213.     this.newRowCellIndent_ = 0;
  214.   }
  215.  
  216.   TablePrinter.prototype = {
  217.     /**
  218.      * Sets the number of blank cells to add after each call to addRow.
  219.      */
  220.     setNewRowCellIndent: function(newRowCellIndent) {
  221.       this.newRowCellIndent_ = newRowCellIndent;
  222.     },
  223.  
  224.     /**
  225.      * Starts a new row.
  226.      */
  227.     addRow: function() {
  228.       this.rows_.push([]);
  229.       for (var i = 0; i < this.newRowCellIndent_; ++i)
  230.         this.addCell('');
  231.     },
  232.  
  233.     /**
  234.      * Adds a column to the current row, setting its value to cellText.
  235.      *
  236.      * @return {!TablePrinterCell} the cell that was added.
  237.      */
  238.     addCell: function(cellText) {
  239.       var r = this.rows_[this.rows_.length - 1];
  240.       var cell = new TablePrinterCell(cellText);
  241.       r.push(cell);
  242.       return cell;
  243.     },
  244.  
  245.     /**
  246.      * Sets the title displayed at the top of a table.  Titles are optional.
  247.      */
  248.     setTitle: function(title) {
  249.       this.title_ = title;
  250.     },
  251.  
  252.     /**
  253.      * Adds a header row, if not already present, and adds a new column to it,
  254.      * setting its contents to |headerText|.
  255.      *
  256.      * @return {!TablePrinterCell} the cell that was added.
  257.      */
  258.     addHeaderCell: function(headerText) {
  259.       // Insert empty new row at start of |rows_| if currently no header row.
  260.       if (!this.hasHeaderRow_) {
  261.         this.rows_.splice(0, 0, []);
  262.         this.hasHeaderRow_ = true;
  263.       }
  264.       var cell = new TablePrinterCell(headerText);
  265.       this.rows_[0].push(cell);
  266.       return cell;
  267.     },
  268.  
  269.     /**
  270.      * Returns the maximum number of columns this table contains.
  271.      */
  272.     getNumColumns: function() {
  273.       var numColumns = 0;
  274.       for (var i = 0; i < this.rows_.length; ++i) {
  275.         numColumns = Math.max(numColumns, this.rows_[i].length);
  276.       }
  277.       return numColumns;
  278.     },
  279.  
  280.     /**
  281.      * Returns the cell at position (rowIndex, columnIndex), or null if there is
  282.      * no such cell.
  283.      */
  284.     getCell_: function(rowIndex, columnIndex) {
  285.       if (rowIndex >= this.rows_.length)
  286.         return null;
  287.       var row = this.rows_[rowIndex];
  288.       if (columnIndex >= row.length)
  289.         return null;
  290.       return row[columnIndex];
  291.     },
  292.  
  293.     /**
  294.      * Returns true if searchString can be found entirely within a cell.
  295.      * Case insensitive.
  296.      *
  297.      * @param {string} string String to search for, must be lowercase.
  298.      * @return {boolean} True if some cell contains searchString.
  299.      */
  300.     search: function(searchString) {
  301.       var numColumns = this.getNumColumns();
  302.       for (var r = 0; r < this.rows_.length; ++r) {
  303.         for (var c = 0; c < numColumns; ++c) {
  304.           var cell = this.getCell_(r, c);
  305.           if (!cell)
  306.             continue;
  307.           if (cell.text.toLowerCase().indexOf(searchString) != -1)
  308.             return true;
  309.         }
  310.       }
  311.       return false;
  312.     },
  313.  
  314.     /**
  315.      * Prints a formatted text representation of the table data to the
  316.      * node |parent|.  |spacing| indicates number of extra spaces, if any,
  317.      * to add between columns.
  318.      */
  319.     toText: function(spacing, parent) {
  320.       var pre = addNode(parent, 'pre');
  321.       var numColumns = this.getNumColumns();
  322.  
  323.       // Figure out the maximum width of each column.
  324.       var columnWidths = [];
  325.       columnWidths.length = numColumns;
  326.       for (var i = 0; i < numColumns; ++i)
  327.         columnWidths[i] = 0;
  328.  
  329.       // If header row is present, temporarily add a spacer row to |rows_|.
  330.       if (this.hasHeaderRow_) {
  331.         var headerSpacerRow = [];
  332.         for (var c = 0; c < numColumns; ++c) {
  333.           var cell = this.getCell_(0, c);
  334.           if (!cell)
  335.             continue;
  336.           var spacerStr = makeRepeatedString('-', cell.text.length);
  337.           headerSpacerRow.push(new TablePrinterCell(spacerStr));
  338.         }
  339.         this.rows_.splice(1, 0, headerSpacerRow);
  340.       }
  341.  
  342.       var numRows = this.rows_.length;
  343.       for (var c = 0; c < numColumns; ++c) {
  344.         for (var r = 0; r < numRows; ++r) {
  345.           var cell = this.getCell_(r, c);
  346.           if (cell && !cell.allowOverflow) {
  347.             columnWidths[c] = Math.max(columnWidths[c], cell.text.length);
  348.           }
  349.         }
  350.       }
  351.  
  352.       var out = [];
  353.  
  354.       // Print title, if present.
  355.       if (this.title_) {
  356.         var titleSpacerStr = makeRepeatedString('-', this.title_.length);
  357.         out.push(titleSpacerStr);
  358.         out.push('\n');
  359.         out.push(this.title_);
  360.         out.push('\n');
  361.         out.push(titleSpacerStr);
  362.         out.push('\n');
  363.       }
  364.  
  365.       // Print each row.
  366.       var spacingStr = makeRepeatedString(' ', spacing);
  367.       for (var r = 0; r < numRows; ++r) {
  368.         for (var c = 0; c < numColumns; ++c) {
  369.           var cell = this.getCell_(r, c);
  370.           if (cell) {
  371.             // Pad the cell with spaces to make it fit the maximum column width.
  372.             var padding = columnWidths[c] - cell.text.length;
  373.             var paddingStr = makeRepeatedString(' ', padding);
  374.  
  375.             if (cell.alignRight)
  376.               out.push(paddingStr);
  377.             if (cell.link) {
  378.               // Output all previous text, and clear |out|.
  379.               addTextNode(pre, out.join(''));
  380.               out = [];
  381.  
  382.               var linkNode = addNodeWithText(pre, 'a', cell.text);
  383.               linkNode.href = cell.link;
  384.             } else {
  385.               out.push(cell.text);
  386.             }
  387.             if (!cell.alignRight)
  388.               out.push(paddingStr);
  389.             out.push(spacingStr);
  390.           }
  391.         }
  392.         out.push('\n');
  393.       }
  394.  
  395.       // Remove spacer row under the header row, if one was added.
  396.       if (this.hasHeaderRow_)
  397.         this.rows_.splice(1, 1);
  398.  
  399.       addTextNode(pre, out.join(''));
  400.     },
  401.  
  402.     /**
  403.      * Adds a new HTML table to the node |parent| using the specified style.
  404.      */
  405.     toHTML: function(parent, style) {
  406.       var numRows = this.rows_.length;
  407.       var numColumns = this.getNumColumns();
  408.  
  409.       var table = addNode(parent, 'table');
  410.       table.setAttribute('class', style);
  411.  
  412.       var thead = addNode(table, 'thead');
  413.       var tbody = addNode(table, 'tbody');
  414.  
  415.       // Add title, if needed.
  416.       if (this.title_) {
  417.         var tableTitleRow = addNode(thead, 'tr');
  418.         var tableTitle = addNodeWithText(tableTitleRow, 'th', this.title_);
  419.         tableTitle.colSpan = numColumns;
  420.         tableTitle.classList.add('title');
  421.       }
  422.  
  423.       // Fill table body, adding header row first, if needed.
  424.       for (var r = 0; r < numRows; ++r) {
  425.         var cellType;
  426.         var row;
  427.         if (r == 0 && this.hasHeaderRow_) {
  428.           row = addNode(thead, 'tr');
  429.           cellType = 'th';
  430.         } else {
  431.           row = addNode(tbody, 'tr');
  432.           cellType = 'td';
  433.         }
  434.         for (var c = 0; c < numColumns; ++c) {
  435.           var cell = this.getCell_(r, c);
  436.           if (cell) {
  437.             var tableCell = addNode(row, cellType, cell.text);
  438.             if (cell.alignRight)
  439.               tableCell.alignRight = true;
  440.             // If allowing overflow on the rightmost cell of a row,
  441.             // make the cell span the rest of the columns.  Otherwise,
  442.             // ignore the flag.
  443.             if (cell.allowOverflow && !this.getCell_(r, c + 1))
  444.               tableCell.colSpan = numColumns - c;
  445.             if (cell.link) {
  446.               var linkNode = addNodeWithText(tableCell, 'a', cell.text);
  447.               linkNode.href = cell.link;
  448.             } else {
  449.               addTextNode(tableCell, cell.text);
  450.             }
  451.           }
  452.         }
  453.       }
  454.       return table;
  455.     }
  456.   };
  457.  
  458.   /**
  459.    * Links are only used in HTML tables.
  460.    */
  461.   function TablePrinterCell(value) {
  462.     this.text = '' + value;
  463.     this.link = null;
  464.     this.alignRight = false;
  465.     this.allowOverflow = false;
  466.   }
  467.  
  468.   return TablePrinter;
  469. })();
  470.  
  471. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  472. // Use of this source code is governed by a BSD-style license that can be
  473. // found in the LICENSE file.
  474.  
  475. /**
  476.  * Base class to represent a "view". A view is an absolutely positioned box on
  477.  * the page.
  478.  */
  479. var View = (function() {
  480.   'use strict';
  481.  
  482.   /**
  483.    * @constructor
  484.    */
  485.   function View() {
  486.     this.isVisible_ = true;
  487.   }
  488.  
  489.   View.prototype = {
  490.     /**
  491.      * Called to reposition the view on the page. Measurements are in pixels.
  492.      */
  493.     setGeometry: function(left, top, width, height) {
  494.       this.left_ = left;
  495.       this.top_ = top;
  496.       this.width_ = width;
  497.       this.height_ = height;
  498.     },
  499.  
  500.     /**
  501.      * Called to show/hide the view.
  502.      */
  503.     show: function(isVisible) {
  504.       this.isVisible_ = isVisible;
  505.     },
  506.  
  507.     isVisible: function() {
  508.       return this.isVisible_;
  509.     },
  510.  
  511.     /**
  512.      * Method of the observer class.
  513.      *
  514.      * Called to check if an observer needs the data it is
  515.      * observing to be actively updated.
  516.      */
  517.     isActive: function() {
  518.       return this.isVisible();
  519.     },
  520.  
  521.     getLeft: function() {
  522.       return this.left_;
  523.     },
  524.  
  525.     getTop: function() {
  526.       return this.top_;
  527.     },
  528.  
  529.     getWidth: function() {
  530.       return this.width_;
  531.     },
  532.  
  533.     getHeight: function() {
  534.       return this.height_;
  535.     },
  536.  
  537.     getRight: function() {
  538.       return this.getLeft() + this.getWidth();
  539.     },
  540.  
  541.     getBottom: function() {
  542.       return this.getTop() + this.getHeight();
  543.     },
  544.  
  545.     setParameters: function(params) {},
  546.  
  547.     /**
  548.      * Called when loading a log file, after clearing all events, but before
  549.      * loading the new ones.  |polledData| contains the data from all
  550.      * PollableData helpers.  |tabData| contains the data for the particular
  551.      * tab.  |logDump| is the entire log dump, which includes the other two
  552.      * values.  It's included separately so most views don't have to depend on
  553.      * its specifics.
  554.      */
  555.     onLoadLogStart: function(polledData, tabData, logDump) {
  556.     },
  557.  
  558.     /**
  559.      * Called as the final step of loading a log file.  Arguments are the same
  560.      * as onLoadLogStart.  Returns true to indicate the tab should be shown,
  561.      * false otherwise.
  562.      */
  563.     onLoadLogFinish: function(polledData, tabData, logDump) {
  564.       return false;
  565.     }
  566.   };
  567.  
  568.   return View;
  569. })();
  570.  
  571. //-----------------------------------------------------------------------------
  572.  
  573. /**
  574.  * DivView is an implementation of View that wraps a DIV.
  575.  */
  576. var DivView = (function() {
  577.   'use strict';
  578.  
  579.   // We inherit from View.
  580.   var superClass = View;
  581.  
  582.   /**
  583.    * @constructor
  584.    */
  585.   function DivView(divId) {
  586.     // Call superclass's constructor.
  587.     superClass.call(this);
  588.  
  589.     this.node_ = $(divId);
  590.     if (!this.node_)
  591.       throw new Error('Element ' + divId + ' not found');
  592.  
  593.     // Initialize the default values to those of the DIV.
  594.     this.width_ = this.node_.offsetWidth;
  595.     this.height_ = this.node_.offsetHeight;
  596.     this.isVisible_ = this.node_.style.display != 'none';
  597.   }
  598.  
  599.   DivView.prototype = {
  600.     // Inherit the superclass's methods.
  601.     __proto__: superClass.prototype,
  602.  
  603.     setGeometry: function(left, top, width, height) {
  604.       superClass.prototype.setGeometry.call(this, left, top, width, height);
  605.  
  606.       this.node_.style.position = 'absolute';
  607.       setNodePosition(this.node_, left, top, width, height);
  608.     },
  609.  
  610.     show: function(isVisible) {
  611.       superClass.prototype.show.call(this, isVisible);
  612.       setNodeDisplay(this.node_, isVisible);
  613.     },
  614.  
  615.     /**
  616.      * Returns the wrapped DIV
  617.      */
  618.     getNode: function() {
  619.       return this.node_;
  620.     }
  621.   };
  622.  
  623.   return DivView;
  624. })();
  625.  
  626.  
  627. //-----------------------------------------------------------------------------
  628.  
  629. /**
  630.  * Implementation of View that sizes its child to fit the entire window.
  631.  *
  632.  * @param {!View} childView The child view.
  633.  */
  634. var WindowView = (function() {
  635.   'use strict';
  636.  
  637.   // We inherit from View.
  638.   var superClass = View;
  639.  
  640.   /**
  641.    * @constructor
  642.    */
  643.   function WindowView(childView) {
  644.     // Call superclass's constructor.
  645.     superClass.call(this);
  646.  
  647.     this.childView_ = childView;
  648.     window.addEventListener('resize', this.resetGeometry.bind(this), true);
  649.   }
  650.  
  651.   WindowView.prototype = {
  652.     // Inherit the superclass's methods.
  653.     __proto__: superClass.prototype,
  654.  
  655.     setGeometry: function(left, top, width, height) {
  656.       superClass.prototype.setGeometry.call(this, left, top, width, height);
  657.       this.childView_.setGeometry(left, top, width, height);
  658.     },
  659.  
  660.     show: function() {
  661.       superClass.prototype.show.call(this, isVisible);
  662.       this.childView_.show(isVisible);
  663.     },
  664.  
  665.     resetGeometry: function() {
  666.       this.setGeometry(0, 0, window.innerWidth, window.innerHeight);
  667.     }
  668.   };
  669.  
  670.   return WindowView;
  671. })();
  672.  
  673. /**
  674.  * View that positions two views vertically. The top view should be
  675.  * fixed-height, and the bottom view will fill the remainder of the space.
  676.  *
  677.  *  +-----------------------------------+
  678.  *  |            topView                |
  679.  *  +-----------------------------------+
  680.  *  |                                   |
  681.  *  |                                   |
  682.  *  |                                   |
  683.  *  |          bottomView               |
  684.  *  |                                   |
  685.  *  |                                   |
  686.  *  |                                   |
  687.  *  |                                   |
  688.  *  +-----------------------------------+
  689.  */
  690. var VerticalSplitView = (function() {
  691.   'use strict';
  692.  
  693.   // We inherit from View.
  694.   var superClass = View;
  695.  
  696.   /**
  697.    * @param {!View} topView The top view.
  698.    * @param {!View} bottomView The bottom view.
  699.    * @constructor
  700.    */
  701.   function VerticalSplitView(topView, bottomView) {
  702.     // Call superclass's constructor.
  703.     superClass.call(this);
  704.  
  705.     this.topView_ = topView;
  706.     this.bottomView_ = bottomView;
  707.   }
  708.  
  709.   VerticalSplitView.prototype = {
  710.     // Inherit the superclass's methods.
  711.     __proto__: superClass.prototype,
  712.  
  713.     setGeometry: function(left, top, width, height) {
  714.       superClass.prototype.setGeometry.call(this, left, top, width, height);
  715.  
  716.       var fixedHeight = this.topView_.getHeight();
  717.       this.topView_.setGeometry(left, top, width, fixedHeight);
  718.  
  719.       this.bottomView_.setGeometry(
  720.           left, top + fixedHeight, width, height - fixedHeight);
  721.     },
  722.  
  723.     show: function(isVisible) {
  724.       superClass.prototype.show.call(this, isVisible);
  725.  
  726.       this.topView_.show(isVisible);
  727.       this.bottomView_.show(isVisible);
  728.     }
  729.   };
  730.  
  731.   return VerticalSplitView;
  732. })();
  733.  
  734. /**
  735.  * View that positions two views horizontally. The left view should be
  736.  * fixed-width, and the right view will fill the remainder of the space.
  737.  *
  738.  *  +----------+--------------------------+
  739.  *  |          |                          |
  740.  *  |          |                          |
  741.  *  |          |                          |
  742.  *  | leftView |       rightView          |
  743.  *  |          |                          |
  744.  *  |          |                          |
  745.  *  |          |                          |
  746.  *  |          |                          |
  747.  *  |          |                          |
  748.  *  |          |                          |
  749.  *  |          |                          |
  750.  *  +----------+--------------------------+
  751.  */
  752. var HorizontalSplitView = (function() {
  753.   'use strict';
  754.  
  755.   // We inherit from View.
  756.   var superClass = View;
  757.  
  758.   /**
  759.    * @param {!View} leftView The left view.
  760.    * @param {!View} rightView The right view.
  761.    * @constructor
  762.    */
  763.   function HorizontalSplitView(leftView, rightView) {
  764.     // Call superclass's constructor.
  765.     superClass.call(this);
  766.  
  767.     this.leftView_ = leftView;
  768.     this.rightView_ = rightView;
  769.   }
  770.  
  771.   HorizontalSplitView.prototype = {
  772.     // Inherit the superclass's methods.
  773.     __proto__: superClass.prototype,
  774.  
  775.     setGeometry: function(left, top, width, height) {
  776.       superClass.prototype.setGeometry.call(this, left, top, width, height);
  777.  
  778.       var fixedWidth = this.leftView_.getWidth();
  779.       this.leftView_.setGeometry(left, top, fixedWidth, height);
  780.  
  781.       this.rightView_.setGeometry(
  782.           left + fixedWidth, top, width - fixedWidth, height);
  783.     },
  784.  
  785.     show: function(isVisible) {
  786.       superClass.prototype.show.call(this, isVisible);
  787.  
  788.       this.leftView_.show(isVisible);
  789.       this.rightView_.show(isVisible);
  790.     }
  791.   };
  792.  
  793.   return HorizontalSplitView;
  794. })();
  795.  
  796. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  797. // Use of this source code is governed by a BSD-style license that can be
  798. // found in the LICENSE file.
  799.  
  800. /**
  801.  * Class to handle display and placement of a div that appears under the cursor
  802.  * when it overs over a specied element.  The div always appears below and to
  803.  * the left of the cursor.
  804.  */
  805. var MouseOverHelp = (function() {
  806.   'use strict';
  807.  
  808.   /**
  809.    * @param {string} helpDivId Name of the div to position and display
  810.    * @param {string} mouseOverElementId Name the element that displays the
  811.    *     |helpDivId| div on mouse over.
  812.    * @constructor
  813.    */
  814.   function MouseOverHelp(helpDivId, mouseOverElementId) {
  815.     this.node_ = $(helpDivId);
  816.  
  817.     $(mouseOverElementId).onmouseover = this.onMouseOver.bind(this);
  818.     $(mouseOverElementId).onmouseout = this.onMouseOut.bind(this);
  819.  
  820.     this.show(false);
  821.   }
  822.  
  823.   MouseOverHelp.prototype = {
  824.     /**
  825.      * Positions and displays the div, if not already visible.
  826.      * @param {MouseEvent} event Mouse event that triggered the call.
  827.      */
  828.     onMouseOver: function(event) {
  829.       if (this.isVisible_)
  830.         return;
  831.  
  832.       this.node_.style.position = 'absolute';
  833.  
  834.       this.show(true);
  835.  
  836.       this.node_.style.left = (event.clientX + 15).toFixed(0) + 'px';
  837.       this.node_.style.top = event.clientY.toFixed(0) + 'px';
  838.     },
  839.  
  840.     /**
  841.      * Hides the div when the cursor leaves the hover element.
  842.      * @param {MouseEvent} event Mouse event that triggered the call.
  843.      */
  844.     onMouseOut: function(event) {
  845.       this.show(false);
  846.     },
  847.  
  848.     /**
  849.      * Sets the div's visibility.
  850.      * @param {boolean} isVisible True if the help div should be shown.
  851.      */
  852.     show: function(isVisible) {
  853.       setNodeDisplay(this.node_, isVisible);
  854.       this.isVisible_ = isVisible;
  855.     },
  856.   };
  857.  
  858.   return MouseOverHelp;
  859. })();
  860.  
  861. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  862. // Use of this source code is governed by a BSD-style license that can be
  863. // found in the LICENSE file.
  864.  
  865. var TabSwitcherView = (function() {
  866.   'use strict';
  867.  
  868.   // We inherit from View.
  869.   var superClass = View;
  870.  
  871.   /**
  872.    * TabSwitcherView is an implementation of View that handles tab switching.
  873.    *
  874.    * It is comprised of many views (tabs), only one of which is visible at a
  875.    * time.
  876.    *
  877.    * This view represents solely the selected tab's content area -- a separate
  878.    * view needs to be maintained for the tab handles.
  879.    *
  880.    * @constructor
  881.    */
  882.   function TabSwitcherView() {
  883.     superClass.call(this);
  884.     this.tabs_ = [];
  885.   }
  886.  
  887.   TabSwitcherView.prototype = {
  888.     // Inherit the superclass's methods.
  889.     __proto__: superClass.prototype,
  890.  
  891.     setGeometry: function(left, top, width, height) {
  892.       superClass.prototype.setGeometry.call(this, left, top, width, height);
  893.  
  894.       // Position each of the tabs content areas.
  895.       for (var i = 0; i < this.tabs_.length; ++i) {
  896.         var tab = this.tabs_[i];
  897.         tab.contentView.setGeometry(left, top, width, height);
  898.       }
  899.     },
  900.  
  901.     show: function(isVisible) {
  902.       superClass.prototype.show.call(this, isVisible);
  903.  
  904.       var activeTab = this.findActiveTab();
  905.       if (activeTab)
  906.         activeTab.contentView.show(isVisible);
  907.     },
  908.  
  909.     /**
  910.      * Adds a new tab (initially hidden).
  911.      *
  912.      * @param {String} id The ID for DOM node that will be made clickable to
  913.      *                    select this tab. This is also the ID we use to
  914.      *                    identify the "tab".
  915.      * @param {!View} view The tab's actual contents.
  916.      */
  917.     addTab: function(id, contentView, switchOnClick, visible) {
  918.       var tab = new TabEntry(id, contentView);
  919.       this.tabs_.push(tab);
  920.  
  921.       if (switchOnClick) {
  922.         // Attach a click handler, used to switch to the tab.
  923.         var self = this;
  924.         tab.getTabHandleNode().onclick = function() {
  925.           self.switchToTab(id, null);
  926.         };
  927.       }
  928.  
  929.       // Start tabs off as hidden.
  930.       tab.contentView.show(false);
  931.  
  932.       this.showTabHandleNode(id, visible);
  933.     },
  934.  
  935.     /**
  936.      * @return {?TabEntry} The currently selected tab, or null if there is none.
  937.      */
  938.     findActiveTab: function() {
  939.       for (var i = 0; i < this.tabs_.length; ++i) {
  940.         var tab = this.tabs_[i];
  941.         if (tab.active)
  942.           return tab;
  943.       }
  944.       return null;
  945.     },
  946.  
  947.     /**
  948.      * @return {?TabEntry} The tab with ID |id|, or null if there is none.
  949.      */
  950.     findTabById: function(id) {
  951.       for (var i = 0; i < this.tabs_.length; ++i) {
  952.         var tab = this.tabs_[i];
  953.         if (tab.id == id)
  954.           return tab;
  955.       }
  956.       return null;
  957.     },
  958.  
  959.     /**
  960.      * Focuses on tab with ID |id|.  |params| is a dictionary that will be
  961.      * passed to the tab's setParameters function, if it's non-null.
  962.      */
  963.     switchToTab: function(id, params) {
  964.       var oldTab = this.findActiveTab();
  965.       if (oldTab)
  966.         oldTab.setSelected(false);
  967.  
  968.       var newTab = this.findTabById(id);
  969.       newTab.setSelected(true);
  970.       if (params)
  971.         newTab.contentView.setParameters(params);
  972.  
  973.       // Update data needed by newly active tab, as it may be
  974.       // significantly out of date.
  975.       if (typeof g_browser != 'undefined' && g_browser.checkForUpdatedInfo)
  976.         g_browser.checkForUpdatedInfo();
  977.     },
  978.  
  979.     getAllTabIds: function() {
  980.       var ids = [];
  981.       for (var i = 0; i < this.tabs_.length; ++i)
  982.         ids.push(this.tabs_[i].id);
  983.       return ids;
  984.     },
  985.  
  986.     /**
  987.      * Shows/hides the DOM node that is used to select the tab.  If the
  988.      * specified tab is the active tab, switches the active tab to the first
  989.      * still visible tab in the tab list.
  990.      */
  991.     showTabHandleNode: function(id, isVisible) {
  992.       var tab = this.findTabById(id);
  993.       if (!isVisible && tab == this.findActiveTab()) {
  994.         for (var i = 0; i < this.tabs_.length; ++i) {
  995.           if (this.tabs_[i].id != id &&
  996.               this.tabs_[i].getTabHandleNode().style.display != 'none') {
  997.             this.switchToTab(this.tabs_[i].id, null);
  998.             break;
  999.           }
  1000.         }
  1001.       }
  1002.       setNodeDisplay(tab.getTabHandleNode(), isVisible);
  1003.     }
  1004.   };
  1005.  
  1006.   //---------------------------------------------------------------------------
  1007.  
  1008.   /**
  1009.    * @constructor
  1010.    */
  1011.   function TabEntry(id, contentView) {
  1012.     this.id = id;
  1013.     this.contentView = contentView;
  1014.   }
  1015.  
  1016.   TabEntry.prototype.setSelected = function(isSelected) {
  1017.     this.active = isSelected;
  1018.     if (isSelected)
  1019.       this.getTabHandleNode().classList.add('selected');
  1020.     else
  1021.       this.getTabHandleNode().classList.remove('selected');
  1022.     this.contentView.show(isSelected);
  1023.   };
  1024.  
  1025.   /**
  1026.    * Returns the DOM node that is used to select the tab.
  1027.    */
  1028.   TabEntry.prototype.getTabHandleNode = function() {
  1029.     return $(this.id);
  1030.   };
  1031.  
  1032.   return TabSwitcherView;
  1033. })();
  1034.  
  1035. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  1036. // Use of this source code is governed by a BSD-style license that can be
  1037. // found in the LICENSE file.
  1038.  
  1039. /**
  1040.  * This view displays options for importing data from a log file.
  1041.  */
  1042. var ImportView = (function() {
  1043.   'use strict';
  1044.  
  1045.   // This is defined in index.html, but for all intents and purposes is part
  1046.   // of this view.
  1047.   var LOAD_LOG_FILE_DROP_TARGET_ID = 'import-view-drop-target';
  1048.  
  1049.   // We inherit from DivView.
  1050.   var superClass = DivView;
  1051.  
  1052.   /**
  1053.    * @constructor
  1054.    */
  1055.   function ImportView() {
  1056.     assertFirstConstructorCall(ImportView);
  1057.  
  1058.     // Call superclass's constructor.
  1059.     superClass.call(this, ImportView.MAIN_BOX_ID);
  1060.  
  1061.     this.loadedDiv_ = $(ImportView.LOADED_DIV_ID);
  1062.  
  1063.     this.loadFileElement_ = $(ImportView.LOAD_LOG_FILE_ID);
  1064.     this.loadFileElement_.onchange = this.logFileChanged.bind(this);
  1065.     this.loadStatusText_ = $(ImportView.LOAD_STATUS_TEXT_ID);
  1066.  
  1067.     var dropTarget = $(LOAD_LOG_FILE_DROP_TARGET_ID);
  1068.     dropTarget.ondragenter = this.onDrag.bind(this);
  1069.     dropTarget.ondragover = this.onDrag.bind(this);
  1070.     dropTarget.ondrop = this.onDrop.bind(this);
  1071.  
  1072.     this.loadedInfoBuildName_ = $(ImportView.LOADED_INFO_BUILD_NAME_ID);
  1073.     this.loadedInfoExportDate_ = $(ImportView.LOADED_INFO_EXPORT_DATE_ID);
  1074.     this.loadedInfoOsType_ = $(ImportView.LOADED_INFO_OS_TYPE_ID);
  1075.     this.loadedInfoCommandLine_ = $(ImportView.LOADED_INFO_COMMAND_LINE_ID);
  1076.     this.loadedInfoUserComments_ = $(ImportView.LOADED_INFO_USER_COMMENTS_ID);
  1077.   }
  1078.  
  1079.   ImportView.TAB_HANDLE_ID = 'tab-handle-import';
  1080.  
  1081.   // IDs for special HTML elements in import_view.html
  1082.   ImportView.MAIN_BOX_ID = 'import-view-tab-content';
  1083.   ImportView.LOADED_DIV_ID = 'import-view-loaded-div';
  1084.   ImportView.LOAD_LOG_FILE_ID = 'import-view-load-log-file';
  1085.   ImportView.LOAD_STATUS_TEXT_ID = 'import-view-load-status-text';
  1086.   ImportView.LOADED_INFO_EXPORT_DATE_ID = 'import-view-export-date';
  1087.   ImportView.LOADED_INFO_BUILD_NAME_ID = 'import-view-build-name';
  1088.   ImportView.LOADED_INFO_OS_TYPE_ID = 'import-view-os-type';
  1089.   ImportView.LOADED_INFO_COMMAND_LINE_ID = 'import-view-command-line';
  1090.   ImportView.LOADED_INFO_USER_COMMENTS_ID = 'import-view-user-comments';
  1091.  
  1092.   cr.addSingletonGetter(ImportView);
  1093.  
  1094.   ImportView.prototype = {
  1095.     // Inherit the superclass's methods.
  1096.     __proto__: superClass.prototype,
  1097.  
  1098.     /**
  1099.      * Called when a log file is loaded, after clearing the old log entries and
  1100.      * loading the new ones.  Returns true to indicate the view should
  1101.      * still be visible.
  1102.      */
  1103.     onLoadLogFinish: function(data, unused, logDump) {
  1104.       setNodeDisplay(this.loadedDiv_, true);
  1105.       this.updateLoadedClientInfo(logDump.userComments);
  1106.       return true;
  1107.     },
  1108.  
  1109.     /**
  1110.      * Called when something is dragged over the drop target.
  1111.      *
  1112.      * Returns false to cancel default browser behavior when a single file is
  1113.      * being dragged.  When this happens, we may not receive a list of files for
  1114.      * security reasons, which is why we allow the |files| array to be empty.
  1115.      */
  1116.     onDrag: function(event) {
  1117.       // NOTE: Use Array.prototype.indexOf here is necessary while WebKit
  1118.       // decides which type of data structure dataTransfer.types will be
  1119.       // (currently between DOMStringList and Array). These have different APIs
  1120.       // so assuming one type or the other was breaking things. See
  1121.       // http://crbug.com/115433. TODO(dbeam): Remove when standardized more.
  1122.       var indexOf = Array.prototype.indexOf;
  1123.       return indexOf.call(event.dataTransfer.types, 'Files') == -1 ||
  1124.              event.dataTransfer.files.length > 1;
  1125.     },
  1126.  
  1127.     /**
  1128.      * Called when something is dropped onto the drop target.  If it's a single
  1129.      * file, tries to load it as a log file.
  1130.      */
  1131.     onDrop: function(event) {
  1132.       var indexOf = Array.prototype.indexOf;
  1133.       if (indexOf.call(event.dataTransfer.types, 'Files') == -1 ||
  1134.           event.dataTransfer.files.length != 1) {
  1135.         return;
  1136.       }
  1137.       event.preventDefault();
  1138.  
  1139.       // Loading a log file may hide the currently active tab.  Switch to the
  1140.       // import tab to prevent this.
  1141.       document.location.hash = 'import';
  1142.  
  1143.       this.loadLogFile(event.dataTransfer.files[0]);
  1144.     },
  1145.  
  1146.     /**
  1147.      * Called when a log file is selected.
  1148.      *
  1149.      * Gets the log file from the input element and tries to read from it.
  1150.      */
  1151.     logFileChanged: function() {
  1152.       this.loadLogFile(this.loadFileElement_.files[0]);
  1153.     },
  1154.  
  1155.     /**
  1156.      * Attempts to read from the File |logFile|.
  1157.      */
  1158.     loadLogFile: function(logFile) {
  1159.       if (logFile) {
  1160.         this.setLoadFileStatus('Loading log...', true);
  1161.         var fileReader = new FileReader();
  1162.  
  1163.         fileReader.onload = this.onLoadLogFile.bind(this, logFile);
  1164.         fileReader.onerror = this.onLoadLogFileError.bind(this);
  1165.  
  1166.         fileReader.readAsText(logFile);
  1167.       }
  1168.     },
  1169.  
  1170.     /**
  1171.      * Displays an error message when unable to read the selected log file.
  1172.      * Also clears the file input control, so the same file can be reloaded.
  1173.      */
  1174.     onLoadLogFileError: function(event) {
  1175.       this.loadFileElement_.value = null;
  1176.       this.setLoadFileStatus(
  1177.           'Error ' + getKeyWithValue(FileError, event.target.error.code) +
  1178.               '.  Unable to read file.',
  1179.           false);
  1180.     },
  1181.  
  1182.     onLoadLogFile: function(logFile, event) {
  1183.       var result = log_util.loadLogFile(event.target.result, logFile.name);
  1184.       this.setLoadFileStatus(result, false);
  1185.     },
  1186.  
  1187.     /**
  1188.      * Sets the load from file status text, displayed below the load file
  1189.      * button, to |text|.  Also enables or disables the load buttons based on
  1190.      * the value of |isLoading|, which must be true if the load process is still
  1191.      * ongoing, and false when the operation has stopped, regardless of success
  1192.      * of failure.  Also, when loading is done, replaces the load button so the
  1193.      * same file can be loaded again.
  1194.      */
  1195.     setLoadFileStatus: function(text, isLoading) {
  1196.       this.enableLoadFileElement_(!isLoading);
  1197.       this.loadStatusText_.textContent = text;
  1198.  
  1199.       if (!isLoading) {
  1200.         // Clear the button, so the same file can be reloaded.  Recreating the
  1201.         // element seems to be the only way to do this.
  1202.         var loadFileElementId = this.loadFileElement_.id;
  1203.         var loadFileElementOnChange = this.loadFileElement_.onchange;
  1204.         this.loadFileElement_.outerHTML = this.loadFileElement_.outerHTML;
  1205.         this.loadFileElement_ = $(loadFileElementId);
  1206.         this.loadFileElement_.onchange = loadFileElementOnChange;
  1207.       }
  1208.  
  1209.       // Style the log output differently depending on what just happened.
  1210.       var pos = text.indexOf('Log loaded.');
  1211.       if (isLoading) {
  1212.         this.loadStatusText_.className = 'import-view-pending-log';
  1213.       } else if (pos == 0) {
  1214.         this.loadStatusText_.className = 'import-view-success-log';
  1215.       } else if (pos != -1) {
  1216.         this.loadStatusText_.className = 'import-view-warning-log';
  1217.       } else {
  1218.         this.loadStatusText_.className = 'import-view-error-log';
  1219.       }
  1220.     },
  1221.  
  1222.     enableLoadFileElement_: function(enabled) {
  1223.       this.loadFileElement_.disabled = !enabled;
  1224.     },
  1225.  
  1226.     /**
  1227.      * Prints some basic information about the environment when the log was
  1228.      * made.
  1229.      */
  1230.     updateLoadedClientInfo: function(userComments) {
  1231.       // Reset all the fields (in case we early-return).
  1232.       this.loadedInfoExportDate_.innerText = '';
  1233.       this.loadedInfoBuildName_.innerText = '';
  1234.       this.loadedInfoOsType_.innerText = '';
  1235.       this.loadedInfoCommandLine_.innerText = '';
  1236.       this.loadedInfoUserComments_.innerText = '';
  1237.  
  1238.       if (typeof(ClientInfo) != 'object')
  1239.         return;
  1240.  
  1241.       timeutil.addNodeWithDate(this.loadedInfoExportDate_,
  1242.                                new Date(ClientInfo.numericDate));
  1243.  
  1244.       var buildName =
  1245.           ClientInfo.name +
  1246.           ' ' + ClientInfo.version +
  1247.           ' (' + ClientInfo.official +
  1248.           ' ' + ClientInfo.cl +
  1249.           ') ' + ClientInfo.version_mod;
  1250.  
  1251.       this.loadedInfoBuildName_.innerText = buildName;
  1252.  
  1253.       this.loadedInfoOsType_.innerText = ClientInfo.os_type;
  1254.       this.loadedInfoCommandLine_.innerText = ClientInfo.command_line;
  1255.  
  1256.       // User comments will not be available when dumped from command line.
  1257.       this.loadedInfoUserComments_.innerText = userComments || '';
  1258.     }
  1259.   };
  1260.  
  1261.   return ImportView;
  1262. })();
  1263.  
  1264. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  1265. // Use of this source code is governed by a BSD-style license that can be
  1266. // found in the LICENSE file.
  1267.  
  1268. /**
  1269.  * This view displays controls for capturing network events.
  1270.  */
  1271. var CaptureView = (function() {
  1272.   'use strict';
  1273.  
  1274.   // We inherit from DivView.
  1275.   var superClass = DivView;
  1276.  
  1277.   /**
  1278.    * @constructor
  1279.    */
  1280.   function CaptureView() {
  1281.     assertFirstConstructorCall(CaptureView);
  1282.  
  1283.     // Call superclass's constructor.
  1284.     superClass.call(this, CaptureView.MAIN_BOX_ID);
  1285.  
  1286.     var byteLoggingCheckbox = $(CaptureView.BYTE_LOGGING_CHECKBOX_ID);
  1287.     byteLoggingCheckbox.onclick = this.onSetByteLogging_.bind(this);
  1288.  
  1289.     $(CaptureView.LIMIT_CHECKBOX_ID).onclick = this.onChangeLimit_.bind(this);
  1290.  
  1291.     $(CaptureView.TIP_ANCHOR_ID).onclick =
  1292.         this.toggleCommandLineTip_.bind(this, CaptureView.TIP_DIV_ID);
  1293.  
  1294.     if (byteLoggingCheckbox.checked) {
  1295.       // The code to display a warning on ExportView relies on bytelogging
  1296.       // being off by default. If this ever changes, the code will need to
  1297.       // be updated.
  1298.       throw 'Not expecting byte logging to be enabled!';
  1299.     }
  1300.  
  1301.     this.onChangeLimit_();
  1302.   }
  1303.  
  1304.   // ID for special HTML element in category_tabs.html
  1305.   CaptureView.TAB_HANDLE_ID = 'tab-handle-capture';
  1306.  
  1307.   // IDs for special HTML elements in capture_view.html
  1308.   CaptureView.MAIN_BOX_ID = 'capture-view-tab-content';
  1309.   CaptureView.BYTE_LOGGING_CHECKBOX_ID = 'capture-view-byte-logging-checkbox';
  1310.   CaptureView.LIMIT_CHECKBOX_ID = 'capture-view-limit-checkbox';
  1311.   CaptureView.TIP_ANCHOR_ID = 'capture-view-tip-anchor';
  1312.   CaptureView.TIP_DIV_ID = 'capture-view-tip-div';
  1313.  
  1314.   cr.addSingletonGetter(CaptureView);
  1315.  
  1316.   CaptureView.prototype = {
  1317.     // Inherit the superclass's methods.
  1318.     __proto__: superClass.prototype,
  1319.  
  1320.     /**
  1321.      * Toggles the visilibity on the command-line tip.
  1322.      */
  1323.     toggleCommandLineTip_: function(divId) {
  1324.       var n = $(divId);
  1325.       var isVisible = n.style.display != 'none';
  1326.       setNodeDisplay(n, !isVisible);
  1327.       return false;  // Prevent default handling of the click.
  1328.     },
  1329.  
  1330.     /**
  1331.      * Called when a log file is loaded, after clearing the old log entries and
  1332.      * loading the new ones.  Returns false to indicate the view should
  1333.      * be hidden.
  1334.      */
  1335.     onLoadLogFinish: function(data) {
  1336.       return false;
  1337.     },
  1338.  
  1339.     /**
  1340.      * Depending on the value of the checkbox, enables or disables logging of
  1341.      * actual bytes transferred.
  1342.      */
  1343.     onSetByteLogging_: function() {
  1344.       var byteLoggingCheckbox = $(CaptureView.BYTE_LOGGING_CHECKBOX_ID);
  1345.  
  1346.       if (byteLoggingCheckbox.checked) {
  1347.         g_browser.setLogLevel(LogLevelType.LOG_ALL);
  1348.  
  1349.         // Once we enable byte logging, all bets are off on what gets captured.
  1350.         // Have the export view warn that the "strip cookies" option is
  1351.         // ineffective from this point on.
  1352.         //
  1353.         // In theory we could clear this warning after unchecking the box and
  1354.         // then deleting all the events which had been captured. We don't
  1355.         // currently do that; if you want the warning to go away, will need to
  1356.         // reload.
  1357.         ExportView.getInstance().showPrivacyWarning();
  1358.       } else {
  1359.         g_browser.setLogLevel(LogLevelType.LOG_ALL_BUT_BYTES);
  1360.       }
  1361.     },
  1362.  
  1363.     onChangeLimit_: function() {
  1364.       var limitCheckbox = $(CaptureView.LIMIT_CHECKBOX_ID);
  1365.  
  1366.       // Default to unlimited.
  1367.       var softLimit = Infinity;
  1368.       var hardLimit = Infinity;
  1369.  
  1370.       if (limitCheckbox.checked) {
  1371.         // The chosen limits are kind of arbitrary. I based it off the
  1372.         // following observation:
  1373.         //   A user-submitted log file which spanned a 7 hour time period
  1374.         //   comprised 778,235 events and required 128MB of JSON.
  1375.         //
  1376.         // That feels too big. Assuming it was representative, then scaling
  1377.         // by a factor of 4 should translate into a 32MB log file and cover
  1378.         // close to 2 hours of events, which feels better.
  1379.         //
  1380.         // A large gap is left between the hardLimit and softLimit to avoid
  1381.         // resetting the events often.
  1382.         hardLimit = 300000;
  1383.         softLimit = 150000;
  1384.       }
  1385.  
  1386.       EventsTracker.getInstance().setLimits(softLimit, hardLimit);
  1387.     },
  1388.   };
  1389.  
  1390.   return CaptureView;
  1391. })();
  1392.  
  1393. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  1394. // Use of this source code is governed by a BSD-style license that can be
  1395. // found in the LICENSE file.
  1396.  
  1397. /**
  1398.  * This view displays options for exporting the captured data.
  1399.  */
  1400. var ExportView = (function() {
  1401.   'use strict';
  1402.  
  1403.   // We inherit from DivView.
  1404.   var superClass = DivView;
  1405.  
  1406.   /**
  1407.    * @constructor
  1408.    */
  1409.   function ExportView() {
  1410.     assertFirstConstructorCall(ExportView);
  1411.  
  1412.     // Call superclass's constructor.
  1413.     superClass.call(this, ExportView.MAIN_BOX_ID);
  1414.  
  1415.     var privacyStrippingCheckbox = $(ExportView.PRIVACY_STRIPPING_CHECKBOX_ID);
  1416.     privacyStrippingCheckbox.onclick =
  1417.         this.onSetPrivacyStripping_.bind(this, privacyStrippingCheckbox);
  1418.  
  1419.     this.saveFileButton_ = $(ExportView.SAVE_FILE_BUTTON_ID);
  1420.     this.saveFileButton_.onclick = this.onSaveFile_.bind(this);
  1421.     this.saveStatusText_ = $(ExportView.SAVE_STATUS_TEXT_ID);
  1422.  
  1423.     this.userCommentsTextArea_ = $(ExportView.USER_COMMENTS_TEXT_AREA_ID);
  1424.  
  1425.     // Track blob for previous log dump so it can be revoked when a new dump is
  1426.     // saved.
  1427.     this.lastBlobURL_ = null;
  1428.  
  1429.     // Cached copy of the last loaded log dump, for use when exporting.
  1430.     this.loadedLogDump_ = null;
  1431.   }
  1432.  
  1433.   // ID for special HTML element in category_tabs.html
  1434.   ExportView.TAB_HANDLE_ID = 'tab-handle-export';
  1435.  
  1436.   // IDs for special HTML elements in export_view.html
  1437.   ExportView.MAIN_BOX_ID = 'export-view-tab-content';
  1438.   ExportView.DOWNLOAD_ANCHOR_ID = 'export-view-download-anchor';
  1439.   ExportView.SAVE_FILE_BUTTON_ID = 'export-view-save-log-file';
  1440.   ExportView.SAVE_STATUS_TEXT_ID = 'export-view-save-status-text';
  1441.   ExportView.PRIVACY_STRIPPING_CHECKBOX_ID =
  1442.       'export-view-privacy-stripping-checkbox';
  1443.   ExportView.USER_COMMENTS_TEXT_AREA_ID = 'export-view-user-comments';
  1444.   ExportView.PRIVACY_WARNING_ID = 'export-view-privacy-warning';
  1445.  
  1446.   cr.addSingletonGetter(ExportView);
  1447.  
  1448.   ExportView.prototype = {
  1449.     // Inherit the superclass's methods.
  1450.     __proto__: superClass.prototype,
  1451.  
  1452.     /**
  1453.      * Depending on the value of the checkbox, enables or disables stripping
  1454.      * cookies and passwords from log dumps and displayed events.
  1455.      */
  1456.     onSetPrivacyStripping_: function(privacyStrippingCheckbox) {
  1457.       SourceTracker.getInstance().setPrivacyStripping(
  1458.           privacyStrippingCheckbox.checked);
  1459.     },
  1460.  
  1461.     /**
  1462.      * When loading a log dump, cache it for future export and continue showing
  1463.      * the ExportView.
  1464.      */
  1465.     onLoadLogFinish: function(polledData, tabData, logDump) {
  1466.       this.loadedLogDump_ = logDump;
  1467.       this.setUserComments_(logDump.userComments);
  1468.       return true;
  1469.     },
  1470.  
  1471.     /**
  1472.      * Sets the save to file status text, displayed below the save to file
  1473.      * button, to |text|.  Also enables or disables the save button based on the
  1474.      * value of |isSaving|, which must be true if the save process is still
  1475.      * ongoing, and false when the operation has stopped, regardless of success
  1476.      * of failure.
  1477.      */
  1478.     setSaveFileStatus: function(text, isSaving) {
  1479.       this.enableSaveFileButton_(!isSaving);
  1480.       this.saveStatusText_.textContent = text;
  1481.     },
  1482.  
  1483.     enableSaveFileButton_: function(enabled) {
  1484.       this.saveFileButton_.disabled = !enabled;
  1485.     },
  1486.  
  1487.     showPrivacyWarning: function() {
  1488.       setNodeDisplay($(ExportView.PRIVACY_WARNING_ID), true);
  1489.       $(ExportView.PRIVACY_STRIPPING_CHECKBOX_ID).checked = false;
  1490.       $(ExportView.PRIVACY_STRIPPING_CHECKBOX_ID).disabled = true;
  1491.  
  1492.       // Updating the checkbox doesn't actually disable privacy stripping, since
  1493.       // the onclick function will not be called.
  1494.       this.onSetPrivacyStripping_($(ExportView.PRIVACY_STRIPPING_CHECKBOX_ID));
  1495.     },
  1496.  
  1497.     /**
  1498.      * If not already busy saving a log dump, triggers asynchronous
  1499.      * generation of log dump and starts waiting for it to complete.
  1500.      */
  1501.     onSaveFile_: function() {
  1502.       if (this.saveFileButton_.disabled)
  1503.         return;
  1504.  
  1505.       // Clean up previous blob, if any, to reduce resource usage.
  1506.       if (this.lastBlobURL_) {
  1507.         window.webkitURL.revokeObjectURL(this.lastBlobURL_);
  1508.         this.lastBlobURL_ = null;
  1509.       }
  1510.       this.createLogDump_(this.onLogDumpCreated_.bind(this));
  1511.     },
  1512.  
  1513.     /**
  1514.      * Creates a log dump, and either synchronously or asynchronously calls
  1515.      * |callback| if it succeeds.  Separate from onSaveFile_ for unit tests.
  1516.      */
  1517.     createLogDump_: function(callback) {
  1518.       // Get an explanation for the dump file (this is mandatory!)
  1519.       var userComments = this.getNonEmptyUserComments_();
  1520.       if (userComments == undefined) {
  1521.         return;
  1522.       }
  1523.  
  1524.       this.setSaveFileStatus('Preparing data...', true);
  1525.  
  1526.       var privacyStripping = SourceTracker.getInstance().getPrivacyStripping();
  1527.  
  1528.       // If we have a cached log dump, update it synchronously.
  1529.       if (this.loadedLogDump_) {
  1530.         var dumpText = log_util.createUpdatedLogDump(userComments,
  1531.                                                      this.loadedLogDump_,
  1532.                                                      privacyStripping);
  1533.         callback(dumpText);
  1534.         return;
  1535.       }
  1536.  
  1537.       // Otherwise, poll information from the browser before creating one.
  1538.       log_util.createLogDumpAsync(userComments,
  1539.                                   callback,
  1540.                                   privacyStripping);
  1541.     },
  1542.  
  1543.     /**
  1544.      * Sets the user comments.
  1545.      */
  1546.     setUserComments_: function(userComments) {
  1547.       this.userCommentsTextArea_.value = userComments;
  1548.     },
  1549.  
  1550.     /**
  1551.      * Fetches the user comments for this dump. If none were entered, warns the
  1552.      * user and returns undefined. Otherwise returns the comments text.
  1553.      */
  1554.     getNonEmptyUserComments_: function() {
  1555.       var value = this.userCommentsTextArea_.value;
  1556.  
  1557.       // Reset the class name in case we had hilighted it earlier.
  1558.       this.userCommentsTextArea_.className = '';
  1559.  
  1560.       // We don't accept empty explanations. We don't care what is entered, as
  1561.       // long as there is something (a single whitespace would work).
  1562.       if (value == '') {
  1563.         // Put a big obnoxious red border around the text area.
  1564.         this.userCommentsTextArea_.className =
  1565.             'export-view-explanation-warning';
  1566.         alert('Please fill in the text field!');
  1567.         return undefined;
  1568.       }
  1569.  
  1570.       return value;
  1571.     },
  1572.  
  1573.     /**
  1574.      * Creates a blob url and starts downloading it.
  1575.      */
  1576.     onLogDumpCreated_: function(dumpText) {
  1577.       var textBlob = new Blob([dumpText], {type: 'octet/stream'});
  1578.       this.lastBlobURL_ = window.webkitURL.createObjectURL(textBlob);
  1579.  
  1580.       // Update the anchor tag and simulate a click on it to start the
  1581.       // download.
  1582.       var downloadAnchor = $(ExportView.DOWNLOAD_ANCHOR_ID);
  1583.       downloadAnchor.href = this.lastBlobURL_;
  1584.       downloadAnchor.click();
  1585.  
  1586.       this.setSaveFileStatus('Dump successful', false);
  1587.     }
  1588.   };
  1589.  
  1590.   return ExportView;
  1591. })();
  1592.  
  1593. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  1594. // Use of this source code is governed by a BSD-style license that can be
  1595. // found in the LICENSE file.
  1596.  
  1597. /**
  1598.  * This view displays information on the HTTP cache.
  1599.  */
  1600. var HttpCacheView = (function() {
  1601.   'use strict';
  1602.  
  1603.   // We inherit from DivView.
  1604.   var superClass = DivView;
  1605.  
  1606.   /**
  1607.    *  @constructor
  1608.    */
  1609.   function HttpCacheView() {
  1610.     assertFirstConstructorCall(HttpCacheView);
  1611.  
  1612.     // Call superclass's constructor.
  1613.     superClass.call(this, HttpCacheView.MAIN_BOX_ID);
  1614.  
  1615.     this.statsDiv_ = $(HttpCacheView.STATS_DIV_ID);
  1616.  
  1617.     // Register to receive http cache info.
  1618.     g_browser.addHttpCacheInfoObserver(this, true);
  1619.   }
  1620.  
  1621.   // ID for special HTML element in category_tabs.html
  1622.   HttpCacheView.TAB_HANDLE_ID = 'tab-handle-http-cache';
  1623.  
  1624.   // IDs for special HTML elements in http_cache_view.html
  1625.   HttpCacheView.MAIN_BOX_ID = 'http-cache-view-tab-content';
  1626.   HttpCacheView.STATS_DIV_ID = 'http-cache-view-cache-stats';
  1627.  
  1628.   cr.addSingletonGetter(HttpCacheView);
  1629.  
  1630.   HttpCacheView.prototype = {
  1631.     // Inherit the superclass's methods.
  1632.     __proto__: superClass.prototype,
  1633.  
  1634.     onLoadLogFinish: function(data) {
  1635.       return this.onHttpCacheInfoChanged(data.httpCacheInfo);
  1636.     },
  1637.  
  1638.     onHttpCacheInfoChanged: function(info) {
  1639.       this.statsDiv_.innerHTML = '';
  1640.  
  1641.       if (!info)
  1642.         return false;
  1643.  
  1644.       // Print the statistics.
  1645.       var statsUl = addNode(this.statsDiv_, 'ul');
  1646.       for (var statName in info.stats) {
  1647.         var li = addNode(statsUl, 'li');
  1648.         addTextNode(li, statName + ': ' + info.stats[statName]);
  1649.       }
  1650.       return true;
  1651.     }
  1652.   };
  1653.  
  1654.   return HttpCacheView;
  1655. })();
  1656.  
  1657. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  1658. // Use of this source code is governed by a BSD-style license that can be
  1659. // found in the LICENSE file.
  1660.  
  1661. /**
  1662.  * This view displays the progress and results from the "connection tester".
  1663.  *
  1664.  *   - Has an input box to specify the URL.
  1665.  *   - Has a button to start running the tests.
  1666.  *   - Shows the set of experiments that have been run so far, and their
  1667.  *     result.
  1668.  */
  1669. var TestView = (function() {
  1670.   'use strict';
  1671.  
  1672.   // We inherit from DivView.
  1673.   var superClass = DivView;
  1674.  
  1675.   /**
  1676.    * @constructor
  1677.    */
  1678.   function TestView() {
  1679.     assertFirstConstructorCall(TestView);
  1680.  
  1681.     // Call superclass's constructor.
  1682.     superClass.call(this, TestView.MAIN_BOX_ID);
  1683.  
  1684.     this.urlInput_ = $(TestView.URL_INPUT_ID);
  1685.     this.summaryDiv_ = $(TestView.SUMMARY_DIV_ID);
  1686.  
  1687.     var form = $(TestView.FORM_ID);
  1688.     form.addEventListener('submit', this.onSubmitForm_.bind(this), false);
  1689.  
  1690.     // Register to test information as it's received.
  1691.     g_browser.addConnectionTestsObserver(this);
  1692.   }
  1693.  
  1694.   // ID for special HTML element in category_tabs.html
  1695.   TestView.TAB_HANDLE_ID = 'tab-handle-tests';
  1696.  
  1697.   // IDs for special HTML elements in test_view.html
  1698.   TestView.MAIN_BOX_ID = 'test-view-tab-content';
  1699.   TestView.FORM_ID = 'test-view-connection-tests-form';
  1700.   TestView.URL_INPUT_ID = 'test-view-url-input';
  1701.   TestView.SUMMARY_DIV_ID = 'test-view-summary';
  1702.   // Needed by tests.
  1703.   TestView.SUBMIT_BUTTON_ID = 'test-view-connection-tests-submit';
  1704.  
  1705.  
  1706.   cr.addSingletonGetter(TestView);
  1707.  
  1708.   TestView.prototype = {
  1709.     // Inherit the superclass's methods.
  1710.     __proto__: superClass.prototype,
  1711.  
  1712.     onSubmitForm_: function(event) {
  1713.       g_browser.sendStartConnectionTests(this.urlInput_.value);
  1714.       event.preventDefault();
  1715.     },
  1716.  
  1717.     /**
  1718.      * Callback for when the connection tests have begun.
  1719.      */
  1720.     onStartedConnectionTestSuite: function() {
  1721.       this.summaryDiv_.innerHTML = '';
  1722.  
  1723.       var p = addNode(this.summaryDiv_, 'p');
  1724.       addTextNode(p, 'Started connection test suite suite on ');
  1725.       timeutil.addNodeWithDate(p, new Date());
  1726.  
  1727.       // Add a table that will hold the individual test results.
  1728.       var table = addNode(this.summaryDiv_, 'table');
  1729.       table.className = 'styled-table';
  1730.       var thead = addNode(table, 'thead');
  1731.       thead.innerHTML = '<tr><th>Result</th><th>Experiment</th>' +
  1732.                         '<th>Error</th><th>Time (ms)</th></tr>';
  1733.  
  1734.       this.tbody_ = addNode(table, 'tbody');
  1735.     },
  1736.  
  1737.     /**
  1738.      * Callback for when an individual test in the suite has begun.
  1739.      */
  1740.     onStartedConnectionTestExperiment: function(experiment) {
  1741.       var tr = addNode(this.tbody_, 'tr');
  1742.  
  1743.       var passFailCell = addNode(tr, 'td');
  1744.  
  1745.       var experimentCell = addNode(tr, 'td');
  1746.  
  1747.       var resultCell = addNode(tr, 'td');
  1748.       addTextNode(resultCell, '?');
  1749.  
  1750.       var dtCell = addNode(tr, 'td');
  1751.       addTextNode(dtCell, '?');
  1752.  
  1753.       // We will fill in result cells with actual values (to replace the
  1754.       // placeholder '?') once the test has completed. For now we just
  1755.       // save references to these cells.
  1756.       this.currentExperimentRow_ = {
  1757.         experimentCell: experimentCell,
  1758.         dtCell: dtCell,
  1759.         resultCell: resultCell,
  1760.         passFailCell: passFailCell,
  1761.         startTime: timeutil.getCurrentTime()
  1762.       };
  1763.  
  1764.       addTextNode(experimentCell, 'Fetch ' + experiment.url);
  1765.  
  1766.       if (experiment.proxy_settings_experiment ||
  1767.           experiment.host_resolver_experiment) {
  1768.         var ul = addNode(experimentCell, 'ul');
  1769.  
  1770.         if (experiment.proxy_settings_experiment) {
  1771.           var li = addNode(ul, 'li');
  1772.           addTextNode(li, experiment.proxy_settings_experiment);
  1773.         }
  1774.  
  1775.         if (experiment.host_resolver_experiment) {
  1776.           var li = addNode(ul, 'li');
  1777.           addTextNode(li, experiment.host_resolver_experiment);
  1778.         }
  1779.       }
  1780.     },
  1781.  
  1782.     /**
  1783.      * Callback for when an individual test in the suite has finished.
  1784.      */
  1785.     onCompletedConnectionTestExperiment: function(experiment, result) {
  1786.       var r = this.currentExperimentRow_;
  1787.  
  1788.       var endTime = timeutil.getCurrentTime();
  1789.  
  1790.       r.dtCell.innerHTML = '';
  1791.       addTextNode(r.dtCell, (endTime - r.startTime));
  1792.  
  1793.       r.resultCell.innerHTML = '';
  1794.  
  1795.       if (result == 0) {
  1796.         r.passFailCell.style.color = 'green';
  1797.         addTextNode(r.passFailCell, 'PASS');
  1798.       } else {
  1799.         addTextNode(r.resultCell,
  1800.                     netErrorToString(result) + ' (' + result + ')');
  1801.         r.passFailCell.style.color = 'red';
  1802.         addTextNode(r.passFailCell, 'FAIL');
  1803.       }
  1804.  
  1805.       this.currentExperimentRow_ = null;
  1806.     },
  1807.  
  1808.     /**
  1809.      * Callback for when the last test in the suite has finished.
  1810.      */
  1811.     onCompletedConnectionTestSuite: function() {
  1812.       var p = addNode(this.summaryDiv_, 'p');
  1813.       addTextNode(p, 'Completed connection test suite suite');
  1814.     }
  1815.   };
  1816.  
  1817.   return TestView;
  1818. })();
  1819.  
  1820. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  1821. // Use of this source code is governed by a BSD-style license that can be
  1822. // found in the LICENSE file.
  1823.  
  1824. /**
  1825.  * HSTS is HTTPS Strict Transport Security: a way for sites to elect to always
  1826.  * use HTTPS. See http://dev.chromium.org/sts
  1827.  *
  1828.  * This UI allows a user to query and update the browser's list of HSTS domains.
  1829.  * It also allows users to query and update the browser's list of public key
  1830.  * pins.
  1831.  */
  1832.  
  1833. var HSTSView = (function() {
  1834.   'use strict';
  1835.  
  1836.   // We inherit from DivView.
  1837.   var superClass = DivView;
  1838.  
  1839.   /**
  1840.    * @constructor
  1841.    */
  1842.   function HSTSView() {
  1843.     assertFirstConstructorCall(HSTSView);
  1844.  
  1845.     // Call superclass's constructor.
  1846.     superClass.call(this, HSTSView.MAIN_BOX_ID);
  1847.  
  1848.     this.addInput_ = $(HSTSView.ADD_INPUT_ID);
  1849.     this.addCheck_ = $(HSTSView.ADD_CHECK_ID);
  1850.     this.addPins_ = $(HSTSView.ADD_PINS_ID);
  1851.     this.deleteInput_ = $(HSTSView.DELETE_INPUT_ID);
  1852.     this.queryInput_ = $(HSTSView.QUERY_INPUT_ID);
  1853.     this.queryOutputDiv_ = $(HSTSView.QUERY_OUTPUT_DIV_ID);
  1854.  
  1855.     var form = $(HSTSView.ADD_FORM_ID);
  1856.     form.addEventListener('submit', this.onSubmitAdd_.bind(this), false);
  1857.  
  1858.     form = $(HSTSView.DELETE_FORM_ID);
  1859.     form.addEventListener('submit', this.onSubmitDelete_.bind(this), false);
  1860.  
  1861.     form = $(HSTSView.QUERY_FORM_ID);
  1862.     form.addEventListener('submit', this.onSubmitQuery_.bind(this), false);
  1863.  
  1864.     g_browser.addHSTSObserver(this);
  1865.   }
  1866.  
  1867.   // ID for special HTML element in category_tabs.html
  1868.   HSTSView.TAB_HANDLE_ID = 'tab-handle-hsts';
  1869.  
  1870.   // IDs for special HTML elements in hsts_view.html
  1871.   HSTSView.MAIN_BOX_ID = 'hsts-view-tab-content';
  1872.   HSTSView.ADD_INPUT_ID = 'hsts-view-add-input';
  1873.   HSTSView.ADD_CHECK_ID = 'hsts-view-check-input';
  1874.   HSTSView.ADD_PINS_ID = 'hsts-view-add-pins';
  1875.   HSTSView.ADD_FORM_ID = 'hsts-view-add-form';
  1876.   HSTSView.ADD_SUBMIT_ID = 'hsts-view-add-submit';
  1877.   HSTSView.DELETE_INPUT_ID = 'hsts-view-delete-input';
  1878.   HSTSView.DELETE_FORM_ID = 'hsts-view-delete-form';
  1879.   HSTSView.DELETE_SUBMIT_ID = 'hsts-view-delete-submit';
  1880.   HSTSView.QUERY_INPUT_ID = 'hsts-view-query-input';
  1881.   HSTSView.QUERY_OUTPUT_DIV_ID = 'hsts-view-query-output';
  1882.   HSTSView.QUERY_FORM_ID = 'hsts-view-query-form';
  1883.   HSTSView.QUERY_SUBMIT_ID = 'hsts-view-query-submit';
  1884.  
  1885.   cr.addSingletonGetter(HSTSView);
  1886.  
  1887.   HSTSView.prototype = {
  1888.     // Inherit the superclass's methods.
  1889.     __proto__: superClass.prototype,
  1890.  
  1891.     onSubmitAdd_: function(event) {
  1892.       g_browser.sendHSTSAdd(this.addInput_.value,
  1893.                             this.addCheck_.checked,
  1894.                             this.addPins_.value);
  1895.       g_browser.sendHSTSQuery(this.addInput_.value);
  1896.       this.queryInput_.value = this.addInput_.value;
  1897.       this.addCheck_.checked = false;
  1898.       this.addInput_.value = '';
  1899.       this.addPins_.value = '';
  1900.       event.preventDefault();
  1901.     },
  1902.  
  1903.     onSubmitDelete_: function(event) {
  1904.       g_browser.sendHSTSDelete(this.deleteInput_.value);
  1905.       this.deleteInput_.value = '';
  1906.       event.preventDefault();
  1907.     },
  1908.  
  1909.     onSubmitQuery_: function(event) {
  1910.       g_browser.sendHSTSQuery(this.queryInput_.value);
  1911.       event.preventDefault();
  1912.     },
  1913.  
  1914.     onHSTSQueryResult: function(result) {
  1915.       if (result.error != undefined) {
  1916.         this.queryOutputDiv_.innerHTML = '';
  1917.         var s = addNode(this.queryOutputDiv_, 'span');
  1918.         s.textContent = result.error;
  1919.         s.style.color = 'red';
  1920.         yellowFade(this.queryOutputDiv_);
  1921.         return;
  1922.       }
  1923.  
  1924.       if (result.result == false) {
  1925.         this.queryOutputDiv_.innerHTML = '<b>Not found</b>';
  1926.         yellowFade(this.queryOutputDiv_);
  1927.         return;
  1928.       }
  1929.  
  1930.       this.queryOutputDiv_.innerHTML = '';
  1931.  
  1932.       var s = addNode(this.queryOutputDiv_, 'span');
  1933.       s.innerHTML = '<b>Found</b>: mode: ';
  1934.  
  1935.       var t = addNode(this.queryOutputDiv_, 'tt');
  1936.       t.textContent = modeToString(result.mode);
  1937.  
  1938.       addTextNode(this.queryOutputDiv_, ' include_subdomains:');
  1939.  
  1940.       t = addNode(this.queryOutputDiv_, 'tt');
  1941.       t.textContent = result.subdomains;
  1942.  
  1943.       addTextNode(this.queryOutputDiv_, ' domain:');
  1944.  
  1945.       t = addNode(this.queryOutputDiv_, 'tt');
  1946.       t.textContent = result.domain;
  1947.  
  1948.       addTextNode(this.queryOutputDiv_, ' pubkey_hashes:');
  1949.  
  1950.       t = addNode(this.queryOutputDiv_, 'tt');
  1951.  
  1952.       // |public_key_hashes| is an old synonym for what is now
  1953.       // |preloaded_spki_hashes|, which in turn is a legacy synonym for
  1954.       // |static_spki_hashes|. Look for all three, and also for
  1955.       // |dynamic_spki_hashes|.
  1956.       if (typeof result.public_key_hashes === 'undefined')
  1957.         result.public_key_hashes = '';
  1958.       if (typeof result.preloaded_spki_hashes === 'undefined')
  1959.         result.preloaded_spki_hashes = '';
  1960.       if (typeof result.static_spki_hashes === 'undefined')
  1961.         result.static_spki_hashes = '';
  1962.       if (typeof result.dynamic_spki_hashes === 'undefined')
  1963.         result.dynamic_spki_hashes = '';
  1964.  
  1965.       var hashes = [];
  1966.       if (result.public_key_hashes)
  1967.         hashes.push(result.public_key_hashes);
  1968.       if (result.preloaded_spki_hashes)
  1969.         hashes.push(result.preloaded_spki_hashes);
  1970.       if (result.static_spki_hashes)
  1971.         hashes.push(result.static_spki_hashes);
  1972.       if (result.dynamic_spki_hashes)
  1973.         hashes.push(result.dynamic_spki_hashes);
  1974.  
  1975.       t.textContent = hashes.join(',');
  1976.       yellowFade(this.queryOutputDiv_);
  1977.     }
  1978.   };
  1979.  
  1980.   function modeToString(m) {
  1981.     // These numbers must match those in
  1982.     // TransportSecurityState::DomainState::UpgradeMode.
  1983.     if (m == 0) {
  1984.       return 'STRICT';
  1985.     } else if (m == 1) {
  1986.       return 'OPPORTUNISTIC';
  1987.     } else {
  1988.       return 'UNKNOWN';
  1989.     }
  1990.   }
  1991.  
  1992.   function yellowFade(element) {
  1993.     element.style.webkitTransitionProperty = 'background-color';
  1994.     element.style.webkitTransitionDuration = '0';
  1995.     element.style.backgroundColor = '#fffccf';
  1996.     setTimeout(function() {
  1997.       element.style.webkitTransitionDuration = '1000ms';
  1998.       element.style.backgroundColor = '#fff';
  1999.     }, 0);
  2000.   }
  2001.  
  2002.   return HSTSView;
  2003. })();
  2004.  
  2005. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2006. // Use of this source code is governed by a BSD-style license that can be
  2007. // found in the LICENSE file.
  2008.  
  2009. /**
  2010.  * This class provides a "bridge" for communicating between the javascript and
  2011.  * the browser.
  2012.  */
  2013. var BrowserBridge = (function() {
  2014.   'use strict';
  2015.  
  2016.   /**
  2017.    * Delay in milliseconds between updates of certain browser information.
  2018.    */
  2019.   var POLL_INTERVAL_MS = 5000;
  2020.  
  2021.   /**
  2022.    * @constructor
  2023.    */
  2024.   function BrowserBridge() {
  2025.     assertFirstConstructorCall(BrowserBridge);
  2026.  
  2027.     // List of observers for various bits of browser state.
  2028.     this.connectionTestsObservers_ = [];
  2029.     this.hstsObservers_ = [];
  2030.     this.constantsObservers_ = [];
  2031.     this.crosONCFileParseObservers_ = [];
  2032.     this.storeDebugLogsObservers_ = [];
  2033.     this.setNetworkDebugModeObservers_ = [];
  2034.     // Unprocessed data received before the constants.  This serves to protect
  2035.     // against passing along data before having information on how to interpret
  2036.     // it.
  2037.     this.earlyReceivedData_ = [];
  2038.  
  2039.     this.pollableDataHelpers_ = {};
  2040.     this.pollableDataHelpers_.proxySettings =
  2041.         new PollableDataHelper('onProxySettingsChanged',
  2042.                                this.sendGetProxySettings.bind(this));
  2043.     this.pollableDataHelpers_.badProxies =
  2044.         new PollableDataHelper('onBadProxiesChanged',
  2045.                                this.sendGetBadProxies.bind(this));
  2046.     this.pollableDataHelpers_.httpCacheInfo =
  2047.         new PollableDataHelper('onHttpCacheInfoChanged',
  2048.                                this.sendGetHttpCacheInfo.bind(this));
  2049.     this.pollableDataHelpers_.hostResolverInfo =
  2050.         new PollableDataHelper('onHostResolverInfoChanged',
  2051.                                this.sendGetHostResolverInfo.bind(this));
  2052.     this.pollableDataHelpers_.socketPoolInfo =
  2053.         new PollableDataHelper('onSocketPoolInfoChanged',
  2054.                                this.sendGetSocketPoolInfo.bind(this));
  2055.     this.pollableDataHelpers_.sessionNetworkStats =
  2056.       new PollableDataHelper('onSessionNetworkStatsChanged',
  2057.                              this.sendGetSessionNetworkStats.bind(this));
  2058.     this.pollableDataHelpers_.historicNetworkStats =
  2059.       new PollableDataHelper('onHistoricNetworkStatsChanged',
  2060.                              this.sendGetHistoricNetworkStats.bind(this));
  2061.     this.pollableDataHelpers_.spdySessionInfo =
  2062.         new PollableDataHelper('onSpdySessionInfoChanged',
  2063.                                this.sendGetSpdySessionInfo.bind(this));
  2064.     this.pollableDataHelpers_.spdyStatus =
  2065.         new PollableDataHelper('onSpdyStatusChanged',
  2066.                                this.sendGetSpdyStatus.bind(this));
  2067.     this.pollableDataHelpers_.spdyAlternateProtocolMappings =
  2068.         new PollableDataHelper('onSpdyAlternateProtocolMappingsChanged',
  2069.                                this.sendGetSpdyAlternateProtocolMappings.bind(
  2070.                                    this));
  2071.     if (cr.isWindows) {
  2072.       this.pollableDataHelpers_.serviceProviders =
  2073.           new PollableDataHelper('onServiceProvidersChanged',
  2074.                                  this.sendGetServiceProviders.bind(this));
  2075.     }
  2076.     this.pollableDataHelpers_.prerenderInfo =
  2077.         new PollableDataHelper('onPrerenderInfoChanged',
  2078.                                this.sendGetPrerenderInfo.bind(this));
  2079.     this.pollableDataHelpers_.httpPipeliningStatus =
  2080.         new PollableDataHelper('onHttpPipeliningStatusChanged',
  2081.                                this.sendGetHttpPipeliningStatus.bind(this));
  2082.  
  2083.     // Setting this to true will cause messages from the browser to be ignored,
  2084.     // and no messages will be sent to the browser, either.  Intended for use
  2085.     // when viewing log files.
  2086.     this.disabled_ = false;
  2087.  
  2088.     // Interval id returned by window.setInterval for polling timer.
  2089.     this.pollIntervalId_ = null;
  2090.   }
  2091.  
  2092.   cr.addSingletonGetter(BrowserBridge);
  2093.  
  2094.   BrowserBridge.prototype = {
  2095.  
  2096.     //--------------------------------------------------------------------------
  2097.     // Messages sent to the browser
  2098.     //--------------------------------------------------------------------------
  2099.  
  2100.     /**
  2101.      * Wraps |chrome.send|.  Doesn't send anything when disabled.
  2102.      */
  2103.     send: function(value1, value2) {
  2104.       if (!this.disabled_) {
  2105.         if (arguments.length == 1) {
  2106.           chrome.send(value1);
  2107.         } else if (arguments.length == 2) {
  2108.           chrome.send(value1, value2);
  2109.         } else {
  2110.           throw 'Unsupported number of arguments.';
  2111.         }
  2112.       }
  2113.     },
  2114.  
  2115.     sendReady: function() {
  2116.       this.send('notifyReady');
  2117.       this.setPollInterval(POLL_INTERVAL_MS);
  2118.     },
  2119.  
  2120.     /**
  2121.      * Some of the data we are interested is not currently exposed as a
  2122.      * stream.  This starts polling those with active observers (visible
  2123.      * views) every |intervalMs|.  Subsequent calls override previous calls
  2124.      * to this function.  If |intervalMs| is 0, stops polling.
  2125.      */
  2126.     setPollInterval: function(intervalMs) {
  2127.       if (this.pollIntervalId_ !== null) {
  2128.         window.clearInterval(this.pollIntervalId_);
  2129.         this.pollIntervalId_ = null;
  2130.       }
  2131.  
  2132.       if (intervalMs > 0) {
  2133.         this.pollIntervalId_ =
  2134.             window.setInterval(this.checkForUpdatedInfo.bind(this, false),
  2135.                                intervalMs);
  2136.       }
  2137.     },
  2138.  
  2139.     sendGetProxySettings: function() {
  2140.       // The browser will call receivedProxySettings on completion.
  2141.       this.send('getProxySettings');
  2142.     },
  2143.  
  2144.     sendReloadProxySettings: function() {
  2145.       this.send('reloadProxySettings');
  2146.     },
  2147.  
  2148.     sendGetBadProxies: function() {
  2149.       // The browser will call receivedBadProxies on completion.
  2150.       this.send('getBadProxies');
  2151.     },
  2152.  
  2153.     sendGetHostResolverInfo: function() {
  2154.       // The browser will call receivedHostResolverInfo on completion.
  2155.       this.send('getHostResolverInfo');
  2156.     },
  2157.  
  2158.     sendRunIPv6Probe: function() {
  2159.       this.send('onRunIPv6Probe');
  2160.     },
  2161.  
  2162.     sendClearBadProxies: function() {
  2163.       this.send('clearBadProxies');
  2164.     },
  2165.  
  2166.     sendClearHostResolverCache: function() {
  2167.       this.send('clearHostResolverCache');
  2168.     },
  2169.  
  2170.     sendClearBrowserCache: function() {
  2171.       this.send('clearBrowserCache');
  2172.     },
  2173.  
  2174.     sendClearAllCache: function() {
  2175.       this.sendClearHostResolverCache();
  2176.       this.sendClearBrowserCache();
  2177.     },
  2178.  
  2179.     sendStartConnectionTests: function(url) {
  2180.       this.send('startConnectionTests', [url]);
  2181.     },
  2182.  
  2183.     sendHSTSQuery: function(domain) {
  2184.       this.send('hstsQuery', [domain]);
  2185.     },
  2186.  
  2187.     sendHSTSAdd: function(domain, include_subdomains, pins) {
  2188.       this.send('hstsAdd', [domain, include_subdomains, pins]);
  2189.     },
  2190.  
  2191.     sendHSTSDelete: function(domain) {
  2192.       this.send('hstsDelete', [domain]);
  2193.     },
  2194.  
  2195.     sendGetHttpCacheInfo: function() {
  2196.       this.send('getHttpCacheInfo');
  2197.     },
  2198.  
  2199.     sendGetSocketPoolInfo: function() {
  2200.       this.send('getSocketPoolInfo');
  2201.     },
  2202.  
  2203.     sendGetSessionNetworkStats: function() {
  2204.       this.send('getSessionNetworkStats');
  2205.     },
  2206.  
  2207.     sendGetHistoricNetworkStats: function() {
  2208.       this.send('getHistoricNetworkStats');
  2209.     },
  2210.  
  2211.     sendCloseIdleSockets: function() {
  2212.       this.send('closeIdleSockets');
  2213.     },
  2214.  
  2215.     sendFlushSocketPools: function() {
  2216.       this.send('flushSocketPools');
  2217.     },
  2218.  
  2219.     sendGetSpdySessionInfo: function() {
  2220.       this.send('getSpdySessionInfo');
  2221.     },
  2222.  
  2223.     sendGetSpdyStatus: function() {
  2224.       this.send('getSpdyStatus');
  2225.     },
  2226.  
  2227.     sendGetSpdyAlternateProtocolMappings: function() {
  2228.       this.send('getSpdyAlternateProtocolMappings');
  2229.     },
  2230.  
  2231.     sendGetServiceProviders: function() {
  2232.       this.send('getServiceProviders');
  2233.     },
  2234.  
  2235.     sendGetPrerenderInfo: function() {
  2236.       this.send('getPrerenderInfo');
  2237.     },
  2238.  
  2239.     enableIPv6: function() {
  2240.       this.send('enableIPv6');
  2241.     },
  2242.  
  2243.     setLogLevel: function(logLevel) {
  2244.       this.send('setLogLevel', ['' + logLevel]);
  2245.     },
  2246.  
  2247.     refreshSystemLogs: function() {
  2248.       this.send('refreshSystemLogs');
  2249.     },
  2250.  
  2251.     getSystemLog: function(log_key, cellId) {
  2252.       this.send('getSystemLog', [log_key, cellId]);
  2253.     },
  2254.  
  2255.     importONCFile: function(fileContent, passcode) {
  2256.       this.send('importONCFile', [fileContent, passcode]);
  2257.     },
  2258.  
  2259.     storeDebugLogs: function() {
  2260.       this.send('storeDebugLogs');
  2261.     },
  2262.  
  2263.     setNetworkDebugMode: function(subsystem) {
  2264.       this.send('setNetworkDebugMode', [subsystem]);
  2265.     },
  2266.  
  2267.     sendGetHttpPipeliningStatus: function() {
  2268.       this.send('getHttpPipeliningStatus');
  2269.     },
  2270.  
  2271.     //--------------------------------------------------------------------------
  2272.     // Messages received from the browser.
  2273.     //--------------------------------------------------------------------------
  2274.  
  2275.     receive: function(command, params) {
  2276.       // Does nothing if disabled.
  2277.       if (this.disabled_)
  2278.         return;
  2279.  
  2280.       // If no constants have been received, and params does not contain the
  2281.       // constants, delay handling the data.
  2282.       if (Constants == null && command != 'receivedConstants') {
  2283.         this.earlyReceivedData_.push({ command: command, params: params });
  2284.         return;
  2285.       }
  2286.  
  2287.       this[command](params);
  2288.  
  2289.       // Handle any data that was received early in the order it was received,
  2290.       // once the constants have been processed.
  2291.       if (this.earlyReceivedData_ != null) {
  2292.         for (var i = 0; i < this.earlyReceivedData_.length; i++) {
  2293.           var command = this.earlyReceivedData_[i];
  2294.           this[command.command](command.params);
  2295.         }
  2296.         this.earlyReceivedData_ = null;
  2297.       }
  2298.     },
  2299.  
  2300.     receivedConstants: function(constants) {
  2301.       for (var i = 0; i < this.constantsObservers_.length; i++)
  2302.         this.constantsObservers_[i].onReceivedConstants(constants);
  2303.     },
  2304.  
  2305.     receivedLogEntries: function(logEntries) {
  2306.       EventsTracker.getInstance().addLogEntries(logEntries);
  2307.     },
  2308.  
  2309.     receivedProxySettings: function(proxySettings) {
  2310.       this.pollableDataHelpers_.proxySettings.update(proxySettings);
  2311.     },
  2312.  
  2313.     receivedBadProxies: function(badProxies) {
  2314.       this.pollableDataHelpers_.badProxies.update(badProxies);
  2315.     },
  2316.  
  2317.     receivedHostResolverInfo: function(hostResolverInfo) {
  2318.       this.pollableDataHelpers_.hostResolverInfo.update(hostResolverInfo);
  2319.     },
  2320.  
  2321.     receivedSocketPoolInfo: function(socketPoolInfo) {
  2322.       this.pollableDataHelpers_.socketPoolInfo.update(socketPoolInfo);
  2323.     },
  2324.  
  2325.     receivedSessionNetworkStats: function(sessionNetworkStats) {
  2326.       this.pollableDataHelpers_.sessionNetworkStats.update(sessionNetworkStats);
  2327.     },
  2328.  
  2329.     receivedHistoricNetworkStats: function(historicNetworkStats) {
  2330.       this.pollableDataHelpers_.historicNetworkStats.update(
  2331.           historicNetworkStats);
  2332.     },
  2333.  
  2334.     receivedSpdySessionInfo: function(spdySessionInfo) {
  2335.       this.pollableDataHelpers_.spdySessionInfo.update(spdySessionInfo);
  2336.     },
  2337.  
  2338.     receivedSpdyStatus: function(spdyStatus) {
  2339.       this.pollableDataHelpers_.spdyStatus.update(spdyStatus);
  2340.     },
  2341.  
  2342.     receivedSpdyAlternateProtocolMappings:
  2343.         function(spdyAlternateProtocolMappings) {
  2344.       this.pollableDataHelpers_.spdyAlternateProtocolMappings.update(
  2345.           spdyAlternateProtocolMappings);
  2346.     },
  2347.  
  2348.     receivedServiceProviders: function(serviceProviders) {
  2349.       this.pollableDataHelpers_.serviceProviders.update(serviceProviders);
  2350.     },
  2351.  
  2352.     receivedStartConnectionTestSuite: function() {
  2353.       for (var i = 0; i < this.connectionTestsObservers_.length; i++)
  2354.         this.connectionTestsObservers_[i].onStartedConnectionTestSuite();
  2355.     },
  2356.  
  2357.     receivedStartConnectionTestExperiment: function(experiment) {
  2358.       for (var i = 0; i < this.connectionTestsObservers_.length; i++) {
  2359.         this.connectionTestsObservers_[i].onStartedConnectionTestExperiment(
  2360.             experiment);
  2361.       }
  2362.     },
  2363.  
  2364.     receivedCompletedConnectionTestExperiment: function(info) {
  2365.       for (var i = 0; i < this.connectionTestsObservers_.length; i++) {
  2366.         this.connectionTestsObservers_[i].onCompletedConnectionTestExperiment(
  2367.             info.experiment, info.result);
  2368.       }
  2369.     },
  2370.  
  2371.     receivedCompletedConnectionTestSuite: function() {
  2372.       for (var i = 0; i < this.connectionTestsObservers_.length; i++)
  2373.         this.connectionTestsObservers_[i].onCompletedConnectionTestSuite();
  2374.     },
  2375.  
  2376.     receivedHSTSResult: function(info) {
  2377.       for (var i = 0; i < this.hstsObservers_.length; i++)
  2378.         this.hstsObservers_[i].onHSTSQueryResult(info);
  2379.     },
  2380.  
  2381.     receivedONCFileParse: function(error) {
  2382.       for (var i = 0; i < this.crosONCFileParseObservers_.length; i++)
  2383.         this.crosONCFileParseObservers_[i].onONCFileParse(error);
  2384.     },
  2385.  
  2386.     receivedStoreDebugLogs: function(status) {
  2387.       for (var i = 0; i < this.storeDebugLogsObservers_.length; i++)
  2388.         this.storeDebugLogsObservers_[i].onStoreDebugLogs(status);
  2389.     },
  2390.  
  2391.     receivedSetNetworkDebugMode: function(status) {
  2392.       for (var i = 0; i < this.setNetworkDebugModeObservers_.length; i++)
  2393.         this.setNetworkDebugModeObservers_[i].onSetNetworkDebugMode(status);
  2394.     },
  2395.  
  2396.     receivedHttpCacheInfo: function(info) {
  2397.       this.pollableDataHelpers_.httpCacheInfo.update(info);
  2398.     },
  2399.  
  2400.     receivedPrerenderInfo: function(prerenderInfo) {
  2401.       this.pollableDataHelpers_.prerenderInfo.update(prerenderInfo);
  2402.     },
  2403.  
  2404.     receivedHttpPipeliningStatus: function(httpPipeliningStatus) {
  2405.       this.pollableDataHelpers_.httpPipeliningStatus.update(
  2406.           httpPipeliningStatus);
  2407.     },
  2408.  
  2409.     //--------------------------------------------------------------------------
  2410.  
  2411.     /**
  2412.      * Prevents receiving/sending events to/from the browser.
  2413.      */
  2414.     disable: function() {
  2415.       this.disabled_ = true;
  2416.       this.setPollInterval(0);
  2417.     },
  2418.  
  2419.     /**
  2420.      * Returns true if the BrowserBridge has been disabled.
  2421.      */
  2422.     isDisabled: function() {
  2423.       return this.disabled_;
  2424.     },
  2425.  
  2426.     /**
  2427.      * Adds a listener of the proxy settings. |observer| will be called back
  2428.      * when data is received, through:
  2429.      *
  2430.      *   observer.onProxySettingsChanged(proxySettings)
  2431.      *
  2432.      * |proxySettings| is a dictionary with (up to) two properties:
  2433.      *
  2434.      *   "original"  -- The settings that chrome was configured to use
  2435.      *                  (i.e. system settings.)
  2436.      *   "effective" -- The "effective" proxy settings that chrome is using.
  2437.      *                  (decides between the manual/automatic modes of the
  2438.      *                  fetched settings).
  2439.      *
  2440.      * Each of these two configurations is formatted as a string, and may be
  2441.      * omitted if not yet initialized.
  2442.      *
  2443.      * If |ignoreWhenUnchanged| is true, data is only sent when it changes.
  2444.      * If it's false, data is sent whenever it's received from the browser.
  2445.      */
  2446.     addProxySettingsObserver: function(observer, ignoreWhenUnchanged) {
  2447.       this.pollableDataHelpers_.proxySettings.addObserver(observer,
  2448.                                                           ignoreWhenUnchanged);
  2449.     },
  2450.  
  2451.     /**
  2452.      * Adds a listener of the proxy settings. |observer| will be called back
  2453.      * when data is received, through:
  2454.      *
  2455.      *   observer.onBadProxiesChanged(badProxies)
  2456.      *
  2457.      * |badProxies| is an array, where each entry has the property:
  2458.      *   badProxies[i].proxy_uri: String identify the proxy.
  2459.      *   badProxies[i].bad_until: The time when the proxy stops being considered
  2460.      *                            bad. Note the time is in time ticks.
  2461.      */
  2462.     addBadProxiesObserver: function(observer, ignoreWhenUnchanged) {
  2463.       this.pollableDataHelpers_.badProxies.addObserver(observer,
  2464.                                                        ignoreWhenUnchanged);
  2465.     },
  2466.  
  2467.     /**
  2468.      * Adds a listener of the host resolver info. |observer| will be called back
  2469.      * when data is received, through:
  2470.      *
  2471.      *   observer.onHostResolverInfoChanged(hostResolverInfo)
  2472.      */
  2473.     addHostResolverInfoObserver: function(observer, ignoreWhenUnchanged) {
  2474.       this.pollableDataHelpers_.hostResolverInfo.addObserver(
  2475.           observer, ignoreWhenUnchanged);
  2476.     },
  2477.  
  2478.     /**
  2479.      * Adds a listener of the socket pool. |observer| will be called back
  2480.      * when data is received, through:
  2481.      *
  2482.      *   observer.onSocketPoolInfoChanged(socketPoolInfo)
  2483.      */
  2484.     addSocketPoolInfoObserver: function(observer, ignoreWhenUnchanged) {
  2485.       this.pollableDataHelpers_.socketPoolInfo.addObserver(observer,
  2486.                                                            ignoreWhenUnchanged);
  2487.     },
  2488.  
  2489.     /**
  2490.      * Adds a listener of the network session. |observer| will be called back
  2491.      * when data is received, through:
  2492.      *
  2493.      *   observer.onSessionNetworkStatsChanged(sessionNetworkStats)
  2494.      */
  2495.     addSessionNetworkStatsObserver: function(observer, ignoreWhenUnchanged) {
  2496.       this.pollableDataHelpers_.sessionNetworkStats.addObserver(
  2497.           observer, ignoreWhenUnchanged);
  2498.     },
  2499.  
  2500.     /**
  2501.      * Adds a listener of persistent network session data. |observer| will be
  2502.      * called back when data is received, through:
  2503.      *
  2504.      *   observer.onHistoricNetworkStatsChanged(historicNetworkStats)
  2505.      */
  2506.     addHistoricNetworkStatsObserver: function(observer, ignoreWhenUnchanged) {
  2507.       this.pollableDataHelpers_.historicNetworkStats.addObserver(
  2508.           observer, ignoreWhenUnchanged);
  2509.     },
  2510.  
  2511.     /**
  2512.      * Adds a listener of the SPDY info. |observer| will be called back
  2513.      * when data is received, through:
  2514.      *
  2515.      *   observer.onSpdySessionInfoChanged(spdySessionInfo)
  2516.      */
  2517.     addSpdySessionInfoObserver: function(observer, ignoreWhenUnchanged) {
  2518.       this.pollableDataHelpers_.spdySessionInfo.addObserver(
  2519.           observer, ignoreWhenUnchanged);
  2520.     },
  2521.  
  2522.     /**
  2523.      * Adds a listener of the SPDY status. |observer| will be called back
  2524.      * when data is received, through:
  2525.      *
  2526.      *   observer.onSpdyStatusChanged(spdyStatus)
  2527.      */
  2528.     addSpdyStatusObserver: function(observer, ignoreWhenUnchanged) {
  2529.       this.pollableDataHelpers_.spdyStatus.addObserver(observer,
  2530.                                                        ignoreWhenUnchanged);
  2531.     },
  2532.  
  2533.     /**
  2534.      * Adds a listener of the AlternateProtocolMappings. |observer| will be
  2535.      * called back when data is received, through:
  2536.      *
  2537.      *   observer.onSpdyAlternateProtocolMappingsChanged(
  2538.      *       spdyAlternateProtocolMappings)
  2539.      */
  2540.     addSpdyAlternateProtocolMappingsObserver: function(observer,
  2541.                                                        ignoreWhenUnchanged) {
  2542.       this.pollableDataHelpers_.spdyAlternateProtocolMappings.addObserver(
  2543.           observer, ignoreWhenUnchanged);
  2544.     },
  2545.  
  2546.     /**
  2547.      * Adds a listener of the service providers info. |observer| will be called
  2548.      * back when data is received, through:
  2549.      *
  2550.      *   observer.onServiceProvidersChanged(serviceProviders)
  2551.      *
  2552.      * Will do nothing if on a platform other than Windows, as service providers
  2553.      * are only present on Windows.
  2554.      */
  2555.     addServiceProvidersObserver: function(observer, ignoreWhenUnchanged) {
  2556.       if (this.pollableDataHelpers_.serviceProviders) {
  2557.         this.pollableDataHelpers_.serviceProviders.addObserver(
  2558.             observer, ignoreWhenUnchanged);
  2559.       }
  2560.     },
  2561.  
  2562.     /**
  2563.      * Adds a listener for the progress of the connection tests.
  2564.      * The observer will be called back with:
  2565.      *
  2566.      *   observer.onStartedConnectionTestSuite();
  2567.      *   observer.onStartedConnectionTestExperiment(experiment);
  2568.      *   observer.onCompletedConnectionTestExperiment(experiment, result);
  2569.      *   observer.onCompletedConnectionTestSuite();
  2570.      */
  2571.     addConnectionTestsObserver: function(observer) {
  2572.       this.connectionTestsObservers_.push(observer);
  2573.     },
  2574.  
  2575.     /**
  2576.      * Adds a listener for the http cache info results.
  2577.      * The observer will be called back with:
  2578.      *
  2579.      *   observer.onHttpCacheInfoChanged(info);
  2580.      */
  2581.     addHttpCacheInfoObserver: function(observer, ignoreWhenUnchanged) {
  2582.       this.pollableDataHelpers_.httpCacheInfo.addObserver(
  2583.           observer, ignoreWhenUnchanged);
  2584.     },
  2585.  
  2586.     /**
  2587.      * Adds a listener for the results of HSTS (HTTPS Strict Transport Security)
  2588.      * queries. The observer will be called back with:
  2589.      *
  2590.      *   observer.onHSTSQueryResult(result);
  2591.      */
  2592.     addHSTSObserver: function(observer) {
  2593.       this.hstsObservers_.push(observer);
  2594.     },
  2595.  
  2596.     /**
  2597.      * Adds a listener for ONC file parse status. The observer will be called
  2598.      * back with:
  2599.      *
  2600.      *   observer.onONCFileParse(error);
  2601.      */
  2602.     addCrosONCFileParseObserver: function(observer) {
  2603.       this.crosONCFileParseObservers_.push(observer);
  2604.     },
  2605.  
  2606.     /**
  2607.      * Adds a listener for storing log file status. The observer will be called
  2608.      * back with:
  2609.      *
  2610.      *   observer.onStoreDebugLogs(status);
  2611.      */
  2612.     addStoreDebugLogsObserver: function(observer) {
  2613.       this.storeDebugLogsObservers_.push(observer);
  2614.     },
  2615.  
  2616.     /**
  2617.      * Adds a listener for network debugging mode status. The observer
  2618.      * will be called back with:
  2619.      *
  2620.      *   observer.onSetNetworkDebugMode(status);
  2621.      */
  2622.     addSetNetworkDebugModeObserver: function(observer) {
  2623.       this.setNetworkDebugModeObservers_.push(observer);
  2624.     },
  2625.  
  2626.     /**
  2627.      * Adds a listener for the received constants event. |observer| will be
  2628.      * called back when the constants are received, through:
  2629.      *
  2630.      *   observer.onReceivedConstants(constants);
  2631.      */
  2632.     addConstantsObserver: function(observer) {
  2633.       this.constantsObservers_.push(observer);
  2634.     },
  2635.  
  2636.     /**
  2637.      * Adds a listener for updated prerender info events
  2638.      * |observer| will be called back with:
  2639.      *
  2640.      *   observer.onPrerenderInfoChanged(prerenderInfo);
  2641.      */
  2642.     addPrerenderInfoObserver: function(observer, ignoreWhenUnchanged) {
  2643.       this.pollableDataHelpers_.prerenderInfo.addObserver(
  2644.           observer, ignoreWhenUnchanged);
  2645.     },
  2646.  
  2647.     /**
  2648.      * Adds a listener of HTTP pipelining status. |observer| will be called
  2649.      * back when data is received, through:
  2650.      *
  2651.      *   observer.onHttpPipelineStatusChanged(httpPipeliningStatus)
  2652.      */
  2653.     addHttpPipeliningStatusObserver: function(observer, ignoreWhenUnchanged) {
  2654.       this.pollableDataHelpers_.httpPipeliningStatus.addObserver(
  2655.           observer, ignoreWhenUnchanged);
  2656.     },
  2657.  
  2658.     /**
  2659.      * If |force| is true, calls all startUpdate functions.  Otherwise, just
  2660.      * runs updates with active observers.
  2661.      */
  2662.     checkForUpdatedInfo: function(force) {
  2663.       for (var name in this.pollableDataHelpers_) {
  2664.         var helper = this.pollableDataHelpers_[name];
  2665.         if (force || helper.hasActiveObserver())
  2666.           helper.startUpdate();
  2667.       }
  2668.     },
  2669.  
  2670.     /**
  2671.      * Calls all startUpdate functions and, if |callback| is non-null,
  2672.      * calls it with the results of all updates.
  2673.      */
  2674.     updateAllInfo: function(callback) {
  2675.       if (callback)
  2676.         new UpdateAllObserver(callback, this.pollableDataHelpers_);
  2677.       this.checkForUpdatedInfo(true);
  2678.     }
  2679.   };
  2680.  
  2681.   /**
  2682.    * This is a helper class used by BrowserBridge, to keep track of:
  2683.    *   - the list of observers interested in some piece of data.
  2684.    *   - the last known value of that piece of data.
  2685.    *   - the name of the callback method to invoke on observers.
  2686.    *   - the update function.
  2687.    * @constructor
  2688.    */
  2689.   function PollableDataHelper(observerMethodName, startUpdateFunction) {
  2690.     this.observerMethodName_ = observerMethodName;
  2691.     this.startUpdate = startUpdateFunction;
  2692.     this.observerInfos_ = [];
  2693.   }
  2694.  
  2695.   PollableDataHelper.prototype = {
  2696.     getObserverMethodName: function() {
  2697.       return this.observerMethodName_;
  2698.     },
  2699.  
  2700.     isObserver: function(object) {
  2701.       for (var i = 0; i < this.observerInfos_.length; i++) {
  2702.         if (this.observerInfos_[i].observer === object)
  2703.           return true;
  2704.       }
  2705.       return false;
  2706.     },
  2707.  
  2708.     /**
  2709.      * If |ignoreWhenUnchanged| is true, we won't send data again until it
  2710.      * changes.
  2711.      */
  2712.     addObserver: function(observer, ignoreWhenUnchanged) {
  2713.       this.observerInfos_.push(new ObserverInfo(observer, ignoreWhenUnchanged));
  2714.     },
  2715.  
  2716.     removeObserver: function(observer) {
  2717.       for (var i = 0; i < this.observerInfos_.length; i++) {
  2718.         if (this.observerInfos_[i].observer === observer) {
  2719.           this.observerInfos_.splice(i, 1);
  2720.           return;
  2721.         }
  2722.       }
  2723.     },
  2724.  
  2725.     /**
  2726.      * Helper function to handle calling all the observers, but ONLY if the data
  2727.      * has actually changed since last time or the observer has yet to receive
  2728.      * any data. This is used for data we received from browser on an update
  2729.      * loop.
  2730.      */
  2731.     update: function(data) {
  2732.       var prevData = this.currentData_;
  2733.       var changed = false;
  2734.  
  2735.       // If the data hasn't changed since last time, will only need to notify
  2736.       // observers that have not yet received any data.
  2737.       if (!prevData || JSON.stringify(prevData) != JSON.stringify(data)) {
  2738.         changed = true;
  2739.         this.currentData_ = data;
  2740.       }
  2741.  
  2742.       // Notify the observers of the change, as needed.
  2743.       for (var i = 0; i < this.observerInfos_.length; i++) {
  2744.         var observerInfo = this.observerInfos_[i];
  2745.         if (changed || !observerInfo.hasReceivedData ||
  2746.             !observerInfo.ignoreWhenUnchanged) {
  2747.           observerInfo.observer[this.observerMethodName_](this.currentData_);
  2748.           observerInfo.hasReceivedData = true;
  2749.         }
  2750.       }
  2751.     },
  2752.  
  2753.     /**
  2754.      * Returns true if one of the observers actively wants the data
  2755.      * (i.e. is visible).
  2756.      */
  2757.     hasActiveObserver: function() {
  2758.       for (var i = 0; i < this.observerInfos_.length; i++) {
  2759.         if (this.observerInfos_[i].observer.isActive())
  2760.           return true;
  2761.       }
  2762.       return false;
  2763.     }
  2764.   };
  2765.  
  2766.   /**
  2767.    * This is a helper class used by PollableDataHelper, to keep track of
  2768.    * each observer and whether or not it has received any data.  The
  2769.    * latter is used to make sure that new observers get sent data on the
  2770.    * update following their creation.
  2771.    * @constructor
  2772.    */
  2773.   function ObserverInfo(observer, ignoreWhenUnchanged) {
  2774.     this.observer = observer;
  2775.     this.hasReceivedData = false;
  2776.     this.ignoreWhenUnchanged = ignoreWhenUnchanged;
  2777.   }
  2778.  
  2779.   /**
  2780.    * This is a helper class used by BrowserBridge to send data to
  2781.    * a callback once data from all polls has been received.
  2782.    *
  2783.    * It works by keeping track of how many polling functions have
  2784.    * yet to receive data, and recording the data as it it received.
  2785.    *
  2786.    * @constructor
  2787.    */
  2788.   function UpdateAllObserver(callback, pollableDataHelpers) {
  2789.     this.callback_ = callback;
  2790.     this.observingCount_ = 0;
  2791.     this.updatedData_ = {};
  2792.  
  2793.     for (var name in pollableDataHelpers) {
  2794.       ++this.observingCount_;
  2795.       var helper = pollableDataHelpers[name];
  2796.       helper.addObserver(this);
  2797.       this[helper.getObserverMethodName()] =
  2798.           this.onDataReceived_.bind(this, helper, name);
  2799.     }
  2800.   }
  2801.  
  2802.   UpdateAllObserver.prototype = {
  2803.     isActive: function() {
  2804.       return true;
  2805.     },
  2806.  
  2807.     onDataReceived_: function(helper, name, data) {
  2808.       helper.removeObserver(this);
  2809.       --this.observingCount_;
  2810.       this.updatedData_[name] = data;
  2811.       if (this.observingCount_ == 0)
  2812.         this.callback_(this.updatedData_);
  2813.     }
  2814.   };
  2815.  
  2816.   return BrowserBridge;
  2817. })();
  2818.  
  2819. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2820. // Use of this source code is governed by a BSD-style license that can be
  2821. // found in the LICENSE file.
  2822.  
  2823. var EventsTracker = (function() {
  2824.   'use strict';
  2825.  
  2826.   /**
  2827.    * This class keeps track of all NetLog events.
  2828.    * It receives events from the browser and when loading a log file, and passes
  2829.    * them on to all its observers.
  2830.    *
  2831.    * @constructor
  2832.    */
  2833.   function EventsTracker() {
  2834.     assertFirstConstructorCall(EventsTracker);
  2835.  
  2836.     this.capturedEvents_ = [];
  2837.     this.observers_ = [];
  2838.  
  2839.     // Controls how large |capturedEvents_| can grow.
  2840.     this.softLimit_ = Infinity;
  2841.     this.hardLimit_ = Infinity;
  2842.   }
  2843.  
  2844.   cr.addSingletonGetter(EventsTracker);
  2845.  
  2846.   EventsTracker.prototype = {
  2847.     /**
  2848.      * Returns a list of all captured events.
  2849.      */
  2850.     getAllCapturedEvents: function() {
  2851.       return this.capturedEvents_;
  2852.     },
  2853.  
  2854.     /**
  2855.      * Returns the number of events that were captured.
  2856.      */
  2857.     getNumCapturedEvents: function() {
  2858.       return this.capturedEvents_.length;
  2859.     },
  2860.  
  2861.     /**
  2862.      * Deletes all the tracked events, and notifies any observers.
  2863.      */
  2864.     deleteAllLogEntries: function() {
  2865.       this.capturedEvents_ = [];
  2866.       for (var i = 0; i < this.observers_.length; ++i)
  2867.         this.observers_[i].onAllLogEntriesDeleted();
  2868.     },
  2869.  
  2870.     /**
  2871.      * Adds captured events, and broadcasts them to any observers.
  2872.      */
  2873.     addLogEntries: function(logEntries) {
  2874.       // When reloading a page, it's possible to receive events before
  2875.       // Constants.  Discard those events, as they can cause the fake
  2876.       // "REQUEST_ALIVE" events for pre-existing requests not be the first
  2877.       // events for those requests.
  2878.       if (Constants == null)
  2879.         return;
  2880.  
  2881.       this.capturedEvents_ = this.capturedEvents_.concat(logEntries);
  2882.       for (var i = 0; i < this.observers_.length; ++i) {
  2883.         this.observers_[i].onReceivedLogEntries(logEntries);
  2884.       }
  2885.  
  2886.       // Check that we haven't grown too big. If so, toss out older events.
  2887.       if (this.getNumCapturedEvents() > this.hardLimit_) {
  2888.         var originalEvents = this.capturedEvents_;
  2889.         this.deleteAllLogEntries();
  2890.         // Delete the oldest events until we reach the soft limit.
  2891.         originalEvents.splice(0, originalEvents.length - this.softLimit_);
  2892.         this.addLogEntries(originalEvents);
  2893.       }
  2894.     },
  2895.  
  2896.     /**
  2897.      * Adds a listener of log entries. |observer| will be called back when new
  2898.      * log data arrives or all entries are deleted:
  2899.      *
  2900.      *   observer.onReceivedLogEntries(entries)
  2901.      *   observer.onAllLogEntriesDeleted()
  2902.      */
  2903.     addLogEntryObserver: function(observer) {
  2904.       this.observers_.push(observer);
  2905.     },
  2906.  
  2907.     /**
  2908.      * Set bounds on the maximum number of events that will be tracked. This
  2909.      * helps to bound the total amount of memory usage, since otherwise
  2910.      * long-running capture sessions can exhaust the renderer's memory and
  2911.      * crash.
  2912.      *
  2913.      * Once |hardLimit| number of events have been captured we do a garbage
  2914.      * collection and toss out old events, bringing our count down to
  2915.      * |softLimit|.
  2916.      *
  2917.      * To log observers this will look like all the events got deleted, and
  2918.      * then subsequently a bunch of new events were received. In other words, it
  2919.      * behaves the same as if the user had simply started logging a bit later
  2920.      * in time!
  2921.      */
  2922.     setLimits: function(softLimit, hardLimit) {
  2923.       if (hardLimit != Infinity && softLimit >= hardLimit)
  2924.         throw 'hardLimit must be greater than softLimit';
  2925.  
  2926.       this.softLimit_ = softLimit;
  2927.       this.hardLimit_ = hardLimit;
  2928.     }
  2929.   };
  2930.  
  2931.   return EventsTracker;
  2932. })();
  2933.  
  2934. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2935. // Use of this source code is governed by a BSD-style license that can be
  2936. // found in the LICENSE file.
  2937.  
  2938. var SourceTracker = (function() {
  2939.   'use strict';
  2940.  
  2941.   /**
  2942.    * This class keeps track of all NetLog events, grouped into per-source
  2943.    * streams. It receives events from EventsTracker, and passes
  2944.    * them on to all its observers.
  2945.    *
  2946.    * @constructor
  2947.    */
  2948.   function SourceTracker() {
  2949.     assertFirstConstructorCall(SourceTracker);
  2950.  
  2951.     // Observers that only want to receive lists of updated SourceEntries.
  2952.     this.sourceEntryObservers_ = [];
  2953.  
  2954.     // True when cookies and authentication information should be removed from
  2955.     // displayed events.  When true, such information should be hidden from
  2956.     // all pages.
  2957.     this.privacyStripping_ = true;
  2958.  
  2959.     this.clearEntries_();
  2960.  
  2961.     EventsTracker.getInstance().addLogEntryObserver(this);
  2962.   }
  2963.  
  2964.   cr.addSingletonGetter(SourceTracker);
  2965.  
  2966.   SourceTracker.prototype = {
  2967.     /**
  2968.      * Clears all log entries and SourceEntries and related state.
  2969.      */
  2970.     clearEntries_: function() {
  2971.       // Used for sorting entries with automatically assigned IDs.
  2972.       this.maxReceivedSourceId_ = 0;
  2973.  
  2974.       // Next unique id to be assigned to a log entry without a source.
  2975.       // Needed to identify associated GUI elements, etc.
  2976.       this.nextSourcelessEventId_ = -1;
  2977.  
  2978.       // Ordered list of log entries.  Needed to maintain original order when
  2979.       // generating log dumps
  2980.       this.capturedEvents_ = [];
  2981.  
  2982.       this.sourceEntries_ = {};
  2983.     },
  2984.  
  2985.     /**
  2986.      * Returns a list of all SourceEntries.
  2987.      */
  2988.     getAllSourceEntries: function() {
  2989.       return this.sourceEntries_;
  2990.     },
  2991.  
  2992.     /**
  2993.      * Returns the description of the specified SourceEntry, or an empty string
  2994.      * if it doesn't exist.
  2995.      */
  2996.     getDescription: function(id) {
  2997.       var entry = this.getSourceEntry(id);
  2998.       if (entry)
  2999.         return entry.getDescription();
  3000.       return '';
  3001.     },
  3002.  
  3003.     /**
  3004.      * Returns the specified SourceEntry.
  3005.      */
  3006.     getSourceEntry: function(id) {
  3007.       return this.sourceEntries_[id];
  3008.     },
  3009.  
  3010.     /**
  3011.      * Sends each entry to all observers and updates |capturedEvents_|.
  3012.      * Also assigns unique ids to log entries without a source.
  3013.      */
  3014.     onReceivedLogEntries: function(logEntries) {
  3015.       // List source entries with new log entries.  Sorted chronologically, by
  3016.       // first new log entry.
  3017.       var updatedSourceEntries = [];
  3018.  
  3019.       var updatedSourceEntryIdMap = {};
  3020.  
  3021.       for (var e = 0; e < logEntries.length; ++e) {
  3022.         var logEntry = logEntries[e];
  3023.  
  3024.         // Assign unique ID, if needed.
  3025.         // TODO(mmenke):  Remove this, and all other code to handle 0 source
  3026.         //                IDs when M19 hits stable.
  3027.         if (logEntry.source.id == 0) {
  3028.           logEntry.source.id = this.nextSourcelessEventId_;
  3029.           --this.nextSourcelessEventId_;
  3030.         } else if (this.maxReceivedSourceId_ < logEntry.source.id) {
  3031.           this.maxReceivedSourceId_ = logEntry.source.id;
  3032.         }
  3033.  
  3034.         // Create/update SourceEntry object.
  3035.         var sourceEntry = this.sourceEntries_[logEntry.source.id];
  3036.         if (!sourceEntry) {
  3037.           sourceEntry = new SourceEntry(logEntry, this.maxReceivedSourceId_);
  3038.           this.sourceEntries_[logEntry.source.id] = sourceEntry;
  3039.         } else {
  3040.           sourceEntry.update(logEntry);
  3041.         }
  3042.  
  3043.         // Add to updated SourceEntry list, if not already in it.
  3044.         if (!updatedSourceEntryIdMap[logEntry.source.id]) {
  3045.           updatedSourceEntryIdMap[logEntry.source.id] = sourceEntry;
  3046.           updatedSourceEntries.push(sourceEntry);
  3047.         }
  3048.       }
  3049.  
  3050.       this.capturedEvents_ = this.capturedEvents_.concat(logEntries);
  3051.       for (var i = 0; i < this.sourceEntryObservers_.length; ++i) {
  3052.         this.sourceEntryObservers_[i].onSourceEntriesUpdated(
  3053.             updatedSourceEntries);
  3054.       }
  3055.     },
  3056.  
  3057.     /**
  3058.      * Called when all log events have been deleted.
  3059.      */
  3060.     onAllLogEntriesDeleted: function() {
  3061.       this.clearEntries_();
  3062.       for (var i = 0; i < this.sourceEntryObservers_.length; ++i)
  3063.         this.sourceEntryObservers_[i].onAllSourceEntriesDeleted();
  3064.     },
  3065.  
  3066.     /**
  3067.      * Sets the value of |privacyStripping_| and informs log observers
  3068.      * of the change.
  3069.      */
  3070.     setPrivacyStripping: function(privacyStripping) {
  3071.       this.privacyStripping_ = privacyStripping;
  3072.       for (var i = 0; i < this.sourceEntryObservers_.length; ++i) {
  3073.         if (this.sourceEntryObservers_[i].onPrivacyStrippingChanged)
  3074.           this.sourceEntryObservers_[i].onPrivacyStrippingChanged();
  3075.       }
  3076.     },
  3077.  
  3078.     /**
  3079.      * Returns whether or not cookies and authentication information should be
  3080.      * displayed for events that contain them.
  3081.      */
  3082.     getPrivacyStripping: function() {
  3083.       return this.privacyStripping_;
  3084.     },
  3085.  
  3086.     /**
  3087.      * Adds a listener of SourceEntries. |observer| will be called back when
  3088.      * SourceEntries are added or modified, source entries are deleted, or
  3089.      * privacy stripping changes:
  3090.      *
  3091.      *   observer.onSourceEntriesUpdated(sourceEntries)
  3092.      *   observer.onAllSourceEntriesDeleted()
  3093.      *   observer.onPrivacyStrippingChanged()
  3094.      */
  3095.     addSourceEntryObserver: function(observer) {
  3096.       this.sourceEntryObservers_.push(observer);
  3097.     }
  3098.   };
  3099.  
  3100.   return SourceTracker;
  3101. })();
  3102.  
  3103. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  3104. // Use of this source code is governed by a BSD-style license that can be
  3105. // found in the LICENSE file.
  3106.  
  3107. /**
  3108.  * This view implements a vertically split display with a draggable divider.
  3109.  *
  3110.  *                  <<-- sizer -->>
  3111.  *
  3112.  *  +----------------------++----------------+
  3113.  *  |                      ||                |
  3114.  *  |                      ||                |
  3115.  *  |                      ||                |
  3116.  *  |                      ||                |
  3117.  *  |       leftView       ||   rightView    |
  3118.  *  |                      ||                |
  3119.  *  |                      ||                |
  3120.  *  |                      ||                |
  3121.  *  |                      ||                |
  3122.  *  |                      ||                |
  3123.  *  +----------------------++----------------+
  3124.  *
  3125.  * @param {!View} leftView The widget to position on the left.
  3126.  * @param {!View} rightView The widget to position on the right.
  3127.  * @param {!DivView} sizerView The widget that will serve as draggable divider.
  3128.  */
  3129. var ResizableVerticalSplitView = (function() {
  3130.   'use strict';
  3131.  
  3132.   // Minimum width to size panels to, in pixels.
  3133.   var MIN_PANEL_WIDTH = 50;
  3134.  
  3135.   // We inherit from View.
  3136.   var superClass = View;
  3137.  
  3138.   /**
  3139.    * @constructor
  3140.    */
  3141.   function ResizableVerticalSplitView(leftView, rightView, sizerView) {
  3142.     // Call superclass's constructor.
  3143.     superClass.call(this);
  3144.  
  3145.     this.leftView_ = leftView;
  3146.     this.rightView_ = rightView;
  3147.     this.sizerView_ = sizerView;
  3148.  
  3149.     this.mouseDragging_ = false;
  3150.     this.touchDragging_ = false;
  3151.  
  3152.     // Setup the "sizer" so it can be dragged left/right to reposition the
  3153.     // vertical split.  The start event must occur within the sizer's node,
  3154.     // but subsequent events may occur anywhere.
  3155.     var node = sizerView.getNode();
  3156.     node.addEventListener('mousedown', this.onMouseDragSizerStart_.bind(this));
  3157.     window.addEventListener('mousemove', this.onMouseDragSizer_.bind(this));
  3158.     window.addEventListener('mouseup', this.onMouseDragSizerEnd_.bind(this));
  3159.  
  3160.     node.addEventListener('touchstart', this.onTouchDragSizerStart_.bind(this));
  3161.     window.addEventListener('touchmove', this.onTouchDragSizer_.bind(this));
  3162.     window.addEventListener('touchend', this.onTouchDragSizerEnd_.bind(this));
  3163.     window.addEventListener('touchcancel',
  3164.                             this.onTouchDragSizerEnd_.bind(this));
  3165.   }
  3166.  
  3167.   ResizableVerticalSplitView.prototype = {
  3168.     // Inherit the superclass's methods.
  3169.     __proto__: superClass.prototype,
  3170.  
  3171.     /**
  3172.      * Sets the width of the left view.
  3173.      * @param {Integer} px The number of pixels
  3174.      */
  3175.     setLeftSplit: function(px) {
  3176.       this.leftSplit_ = px;
  3177.     },
  3178.  
  3179.     /**
  3180.      * Repositions all of the elements to fit the window.
  3181.      */
  3182.     setGeometry: function(left, top, width, height) {
  3183.       superClass.prototype.setGeometry.call(this, left, top, width, height);
  3184.  
  3185.       // If this is the first setGeometry(), initialize the split point at 50%.
  3186.       if (!this.leftSplit_)
  3187.         this.leftSplit_ = parseInt((width / 2).toFixed(0));
  3188.  
  3189.       // Calculate the horizontal split points.
  3190.       var leftboxWidth = this.leftSplit_;
  3191.       var sizerWidth = this.sizerView_.getWidth();
  3192.       var rightboxWidth = width - (leftboxWidth + sizerWidth);
  3193.  
  3194.       // Don't let the right pane get too small.
  3195.       if (rightboxWidth < MIN_PANEL_WIDTH) {
  3196.         rightboxWidth = MIN_PANEL_WIDTH;
  3197.         leftboxWidth = width - (sizerWidth + rightboxWidth);
  3198.       }
  3199.  
  3200.       // Position the boxes using calculated split points.
  3201.       this.leftView_.setGeometry(left, top, leftboxWidth, height);
  3202.       this.sizerView_.setGeometry(this.leftView_.getRight(), top,
  3203.                                   sizerWidth, height);
  3204.       this.rightView_.setGeometry(this.sizerView_.getRight(), top,
  3205.                                   rightboxWidth, height);
  3206.     },
  3207.  
  3208.     show: function(isVisible) {
  3209.       superClass.prototype.show.call(this, isVisible);
  3210.       this.leftView_.show(isVisible);
  3211.       this.sizerView_.show(isVisible);
  3212.       this.rightView_.show(isVisible);
  3213.     },
  3214.  
  3215.     /**
  3216.      * Called once the sizer is clicked on. Starts moving the sizer in response
  3217.      * to future mouse movement.
  3218.      */
  3219.     onMouseDragSizerStart_: function(event) {
  3220.       this.mouseDragging_ = true;
  3221.       event.preventDefault();
  3222.     },
  3223.  
  3224.     /**
  3225.      * Called when the mouse has moved.
  3226.      */
  3227.     onMouseDragSizer_: function(event) {
  3228.       if (!this.mouseDragging_)
  3229.         return;
  3230.       // If dragging has started, move the sizer.
  3231.       this.onDragSizer_(event.pageX);
  3232.       event.preventDefault();
  3233.     },
  3234.  
  3235.     /**
  3236.      * Called once the mouse has been released.
  3237.      */
  3238.     onMouseDragSizerEnd_: function(event) {
  3239.       if (!this.mouseDragging_)
  3240.         return;
  3241.       // Dragging is over.
  3242.       this.mouseDragging_ = false;
  3243.       event.preventDefault();
  3244.     },
  3245.  
  3246.     /**
  3247.      * Called when the user touches the sizer.  Starts moving the sizer in
  3248.      * response to future touch events.
  3249.      */
  3250.     onTouchDragSizerStart_: function(event) {
  3251.       this.touchDragging_ = true;
  3252.       event.preventDefault();
  3253.     },
  3254.  
  3255.     /**
  3256.      * Called when the mouse has moved after dragging started.
  3257.      */
  3258.     onTouchDragSizer_: function(event) {
  3259.       if (!this.touchDragging_)
  3260.         return;
  3261.       // If dragging has started, move the sizer.
  3262.       this.onDragSizer_(event.touches[0].pageX);
  3263.       event.preventDefault();
  3264.     },
  3265.  
  3266.     /**
  3267.      * Called once the user stops touching the screen.
  3268.      */
  3269.     onTouchDragSizerEnd_: function(event) {
  3270.       if (!this.touchDragging_)
  3271.         return;
  3272.       // Dragging is over.
  3273.       this.touchDragging_ = false;
  3274.       event.preventDefault();
  3275.     },
  3276.  
  3277.     /**
  3278.      * Common code used for both mouse and touch dragging.
  3279.      */
  3280.     onDragSizer_: function(pageX) {
  3281.       // Convert from page coordinates, to view coordinates.
  3282.       this.leftSplit_ = (pageX - this.getLeft());
  3283.  
  3284.       // Avoid shrinking the left box too much.
  3285.       this.leftSplit_ = Math.max(this.leftSplit_, MIN_PANEL_WIDTH);
  3286.       // Avoid shrinking the right box too much.
  3287.       this.leftSplit_ = Math.min(
  3288.           this.leftSplit_, this.getWidth() - MIN_PANEL_WIDTH);
  3289.  
  3290.       // Force a layout with the new |leftSplit_|.
  3291.       this.setGeometry(
  3292.           this.getLeft(), this.getTop(), this.getWidth(), this.getHeight());
  3293.     },
  3294.   };
  3295.  
  3296.   return ResizableVerticalSplitView;
  3297. })();
  3298.  
  3299. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  3300. // Use of this source code is governed by a BSD-style license that can be
  3301. // found in the LICENSE file.
  3302.  
  3303. /**
  3304.  * Dictionary of constants (Initialized soon after loading by data from browser,
  3305.  * updated on load log).  The *Types dictionaries map strings to numeric IDs,
  3306.  * while the *TypeNames are the other way around.
  3307.  */
  3308. var EventType = null;
  3309. var EventTypeNames = null;
  3310. var EventPhase = null;
  3311. var EventSourceType = null;
  3312. var EventSourceTypeNames = null;
  3313. var LogLevelType = null;
  3314. var ClientInfo = null;
  3315. var NetError = null;
  3316. var LoadFlag = null;
  3317. var LoadState = null;
  3318. var AddressFamily = null;
  3319.  
  3320. /**
  3321.  * Dictionary of all constants, used for saving log files.
  3322.  */
  3323. var Constants = null;
  3324.  
  3325. /**
  3326.  * Object to communicate between the renderer and the browser.
  3327.  * @type {!BrowserBridge}
  3328.  */
  3329. var g_browser = null;
  3330.  
  3331. /**
  3332.  * This class is the root view object of the page.  It owns all the other
  3333.  * views, and manages switching between them.  It is also responsible for
  3334.  * initializing the views and the BrowserBridge.
  3335.  */
  3336. var MainView = (function() {
  3337.   'use strict';
  3338.  
  3339.   // We inherit from HorizontalSplitView
  3340.   var superClass = HorizontalSplitView;
  3341.  
  3342.   /**
  3343.    * Main entry point. Called once the page has loaded.
  3344.    *  @constructor
  3345.    */
  3346.   function MainView() {
  3347.     assertFirstConstructorCall(MainView);
  3348.  
  3349.     if (hasTouchScreen())
  3350.       document.body.classList.add('touch');
  3351.  
  3352.     // This must be initialized before the tabs, so they can register as
  3353.     // observers.
  3354.     g_browser = BrowserBridge.getInstance();
  3355.  
  3356.     // This must be the first constants observer, so other constants observers
  3357.     // can safely use the globals, rather than depending on walking through
  3358.     // the constants themselves.
  3359.     g_browser.addConstantsObserver(new ConstantsObserver());
  3360.  
  3361.     // This view is a left navigation bar.
  3362.     this.categoryTabSwitcher_ = new TabSwitcherView();
  3363.     var tabs = this.categoryTabSwitcher_;
  3364.  
  3365.     // Call superclass's constructor, initializing the view which lets you tab
  3366.     // between the different sub-views.
  3367.     superClass.call(this,
  3368.                     new DivView(MainView.CATEGORY_TAB_HANDLES_ID),
  3369.                     tabs);
  3370.  
  3371.     // Populate the main tabs.  Even tabs that don't contain information for the
  3372.     // running OS should be created, so they can load log dumps from other
  3373.     // OSes.
  3374.     tabs.addTab(CaptureView.TAB_HANDLE_ID, CaptureView.getInstance(),
  3375.                 false, true);
  3376.     tabs.addTab(ExportView.TAB_HANDLE_ID, ExportView.getInstance(),
  3377.                 false, true);
  3378.     tabs.addTab(ImportView.TAB_HANDLE_ID, ImportView.getInstance(),
  3379.                 false, true);
  3380.     tabs.addTab(ProxyView.TAB_HANDLE_ID, ProxyView.getInstance(),
  3381.                 false, true);
  3382.     tabs.addTab(EventsView.TAB_HANDLE_ID, EventsView.getInstance(),
  3383.                 false, true);
  3384.     tabs.addTab(TimelineView.TAB_HANDLE_ID, TimelineView.getInstance(),
  3385.                 false, true);
  3386.     tabs.addTab(DnsView.TAB_HANDLE_ID, DnsView.getInstance(),
  3387.                 false, true);
  3388.     tabs.addTab(SocketsView.TAB_HANDLE_ID, SocketsView.getInstance(),
  3389.                 false, true);
  3390.     tabs.addTab(SpdyView.TAB_HANDLE_ID, SpdyView.getInstance(), false, true);
  3391.     tabs.addTab(HttpPipelineView.TAB_HANDLE_ID, HttpPipelineView.getInstance(),
  3392.                 false, true);
  3393.     tabs.addTab(HttpCacheView.TAB_HANDLE_ID, HttpCacheView.getInstance(),
  3394.                 false, true);
  3395.     tabs.addTab(ServiceProvidersView.TAB_HANDLE_ID,
  3396.                 ServiceProvidersView.getInstance(), false, cr.isWindows);
  3397.     tabs.addTab(TestView.TAB_HANDLE_ID, TestView.getInstance(), false, true);
  3398.     tabs.addTab(HSTSView.TAB_HANDLE_ID, HSTSView.getInstance(), false, true);
  3399.     tabs.addTab(LogsView.TAB_HANDLE_ID, LogsView.getInstance(),
  3400.                 false, cr.isChromeOS);
  3401.     tabs.addTab(BandwidthView.TAB_HANDLE_ID, BandwidthView.getInstance(),
  3402.                 false, true);
  3403.     tabs.addTab(PrerenderView.TAB_HANDLE_ID, PrerenderView.getInstance(),
  3404.                 false, true);
  3405.     tabs.addTab(CrosView.TAB_HANDLE_ID, CrosView.getInstance(),
  3406.                 false, cr.isChromeOS);
  3407.  
  3408.     // Build a map from the anchor name of each tab handle to its "tab ID".
  3409.     // We will consider navigations to the #hash as a switch tab request.
  3410.     var anchorMap = {};
  3411.     var tabIds = tabs.getAllTabIds();
  3412.     for (var i = 0; i < tabIds.length; ++i) {
  3413.       var aNode = $(tabIds[i]);
  3414.       anchorMap[aNode.hash] = tabIds[i];
  3415.     }
  3416.     // Default the empty hash to the data tab.
  3417.     anchorMap['#'] = anchorMap[''] = ExportView.TAB_HANDLE_ID;
  3418.  
  3419.     window.onhashchange = onUrlHashChange.bind(null, tabs, anchorMap);
  3420.  
  3421.     // Cut out a small vertical strip at the top of the window, to display
  3422.     // a high level status (i.e. if we are capturing events, or displaying a
  3423.     // log file). Below it we will position the main tabs and their content
  3424.     // area.
  3425.     this.statusView_ = StatusView.getInstance(this);
  3426.     var verticalSplitView = new VerticalSplitView(this.statusView_, this);
  3427.     this.statusView_.setLayoutParent(verticalSplitView);
  3428.     var windowView = new WindowView(verticalSplitView);
  3429.  
  3430.     // Trigger initial layout.
  3431.     windowView.resetGeometry();
  3432.  
  3433.     // Select the initial view based on the current URL.
  3434.     window.onhashchange();
  3435.  
  3436.     // Tell the browser that we are ready to start receiving log events.
  3437.     g_browser.sendReady();
  3438.   }
  3439.  
  3440.   // IDs for special HTML elements in index.html
  3441.   MainView.CATEGORY_TAB_HANDLES_ID = 'category-tab-handles';
  3442.  
  3443.   cr.addSingletonGetter(MainView);
  3444.  
  3445.   // Tracks if we're viewing a loaded log file, so views can behave
  3446.   // appropriately.  Global so safe to call during construction.
  3447.   var isViewingLoadedLog = false;
  3448.  
  3449.   MainView.isViewingLoadedLog = function() {
  3450.     return isViewingLoadedLog;
  3451.   };
  3452.  
  3453.   MainView.prototype = {
  3454.     // Inherit the superclass's methods.
  3455.     __proto__: superClass.prototype,
  3456.  
  3457.     // This is exposed both so the log import/export code can enumerate all the
  3458.     // tabs, and for testing.
  3459.     categoryTabSwitcher: function() {
  3460.       return this.categoryTabSwitcher_;
  3461.     },
  3462.  
  3463.     /**
  3464.      * Prevents receiving/sending events to/from the browser, so loaded data
  3465.      * will not be mixed with current Chrome state.  Also hides any interactive
  3466.      * HTML elements that send messages to the browser.  Cannot be undone
  3467.      * without reloading the page.  Must be called before passing loaded data
  3468.      * to the individual views.
  3469.      *
  3470.      * @param {String} opt_fileName The name of the log file that has been
  3471.      *     loaded, if we're loading a log file.
  3472.      */
  3473.     onLoadLog: function(opt_fileName) {
  3474.       isViewingLoadedLog = true;
  3475.  
  3476.       this.stopCapturing();
  3477.       if (opt_fileName != undefined) {
  3478.         // If there's a file name, a log file was loaded, so swap out the status
  3479.         // bar to indicate we're no longer capturing events.  Also disable
  3480.         // hiding cookies, so if the log dump has them, they'll be displayed.
  3481.         this.statusView_.switchToSubView('loaded').setFileName(opt_fileName);
  3482.         $(ExportView.PRIVACY_STRIPPING_CHECKBOX_ID).checked = false;
  3483.         SourceTracker.getInstance().setPrivacyStripping(false);
  3484.       } else {
  3485.         // Otherwise, the "Stop Capturing" button was presumably pressed.
  3486.         // Don't disable hiding cookies, so created log dumps won't have them,
  3487.         // unless the user toggles the option.
  3488.         this.statusView_.switchToSubView('halted');
  3489.       }
  3490.     },
  3491.  
  3492.     switchToViewOnlyMode: function() {
  3493.       // Since this won't be dumped to a file, we don't want to remove
  3494.       // cookies and credentials.
  3495.       log_util.createLogDumpAsync('', log_util.loadLogFile, false);
  3496.     },
  3497.  
  3498.     stopCapturing: function() {
  3499.       g_browser.disable();
  3500.       document.styleSheets[0].insertRule(
  3501.           '.hide-when-not-capturing { display: none; }');
  3502.     }
  3503.   };
  3504.  
  3505.   /**
  3506.    * Takes the current hash in form of "#tab¶m1=value1¶m2=value2&...".
  3507.    * Puts the parameters in an object, and passes the resulting object to
  3508.    * |categoryTabSwitcher|.  Uses tab and |anchorMap| to find a tab ID,
  3509.    * which it also passes to the tab switcher.
  3510.    *
  3511.    * Parameters and values are decoded with decodeURIComponent().
  3512.    */
  3513.   function onUrlHashChange(categoryTabSwitcher, anchorMap) {
  3514.     var parameters = window.location.hash.split('&');
  3515.  
  3516.     var tabId = anchorMap[parameters[0]];
  3517.     if (!tabId)
  3518.       return;
  3519.  
  3520.     // Split each string except the first around the '='.
  3521.     var paramDict = null;
  3522.     for (var i = 1; i < parameters.length; i++) {
  3523.       var paramStrings = parameters[i].split('=');
  3524.       if (paramStrings.length != 2)
  3525.         continue;
  3526.       if (paramDict == null)
  3527.         paramDict = {};
  3528.       var key = decodeURIComponent(paramStrings[0]);
  3529.       var value = decodeURIComponent(paramStrings[1]);
  3530.       paramDict[key] = value;
  3531.     }
  3532.  
  3533.     categoryTabSwitcher.switchToTab(tabId, paramDict);
  3534.   }
  3535.  
  3536.   return MainView;
  3537. })();
  3538.  
  3539. function ConstantsObserver() {}
  3540.  
  3541. /**
  3542.  * Loads all constants from |constants|.  On failure, global dictionaries are
  3543.  * not modifed.
  3544.  * @param {Object} receivedConstants The map of received constants.
  3545.  */
  3546. ConstantsObserver.prototype.onReceivedConstants = function(receivedConstants) {
  3547.   if (!areValidConstants(receivedConstants))
  3548.     return;
  3549.  
  3550.   Constants = receivedConstants;
  3551.  
  3552.   EventType = Constants.logEventTypes;
  3553.   EventTypeNames = makeInverseMap(EventType);
  3554.   EventPhase = Constants.logEventPhase;
  3555.   EventSourceType = Constants.logSourceType;
  3556.   EventSourceTypeNames = makeInverseMap(EventSourceType);
  3557.   LogLevelType = Constants.logLevelType;
  3558.   ClientInfo = Constants.clientInfo;
  3559.   LoadFlag = Constants.loadFlag;
  3560.   NetError = Constants.netError;
  3561.   AddressFamily = Constants.addressFamily;
  3562.   LoadState = Constants.loadState;
  3563.  
  3564.   timeutil.setTimeTickOffset(Constants.timeTickOffset);
  3565. };
  3566.  
  3567. /**
  3568.  * Returns true if it's given a valid-looking constants object.
  3569.  * @param {Object} receivedConstants The received map of constants.
  3570.  * @return {boolean} True if the |receivedConstants| object appears valid.
  3571.  */
  3572. function areValidConstants(receivedConstants) {
  3573.   return typeof(receivedConstants) == 'object' &&
  3574.          typeof(receivedConstants.logEventTypes) == 'object' &&
  3575.          typeof(receivedConstants.clientInfo) == 'object' &&
  3576.          typeof(receivedConstants.logEventPhase) == 'object' &&
  3577.          typeof(receivedConstants.logSourceType) == 'object' &&
  3578.          typeof(receivedConstants.logLevelType) == 'object' &&
  3579.          typeof(receivedConstants.loadFlag) == 'object' &&
  3580.          typeof(receivedConstants.netError) == 'object' &&
  3581.          typeof(receivedConstants.addressFamily) == 'object' &&
  3582.          typeof(receivedConstants.timeTickOffset) == 'string' &&
  3583.          typeof(receivedConstants.logFormatVersion) == 'number';
  3584. }
  3585.  
  3586. /**
  3587.  * Returns the name for netError.
  3588.  *
  3589.  * Example: netErrorToString(-105) would return
  3590.  * "ERR_NAME_NOT_RESOLVED".
  3591.  * @param {number} netError The net error code.
  3592.  * @return {string} The name of the given error.
  3593.  */
  3594. function netErrorToString(netError) {
  3595.   var str = getKeyWithValue(NetError, netError);
  3596.   if (str == '?')
  3597.     return str;
  3598.   return 'ERR_' + str;
  3599. }
  3600.  
  3601. /**
  3602.  * Returns a string representation of |family|.
  3603.  * @param {number} family An AddressFamily
  3604.  * @return {string} A representation of the given family.
  3605.  */
  3606. function addressFamilyToString(family) {
  3607.   var str = getKeyWithValue(AddressFamily, family);
  3608.   // All the address family start with ADDRESS_FAMILY_*.
  3609.   // Strip that prefix since it is redundant and only clutters the output.
  3610.   return str.replace(/^ADDRESS_FAMILY_/, '');
  3611. }
  3612.  
  3613. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  3614. // Use of this source code is governed by a BSD-style license that can be
  3615. // found in the LICENSE file.
  3616.  
  3617. var timeutil = (function() {
  3618.   'use strict';
  3619.  
  3620.   /**
  3621.    * Offset needed to convert event times to Date objects.
  3622.    * Updated whenever constants are loaded.
  3623.    */
  3624.   var timeTickOffset = 0;
  3625.  
  3626.   /**
  3627.    * Sets the offset used to convert tick counts to dates.
  3628.    */
  3629.   function setTimeTickOffset(offset) {
  3630.     // Note that the subtraction by 0 is to cast to a number (probably a float
  3631.     // since the numbers are big).
  3632.     timeTickOffset = offset - 0;
  3633.   }
  3634.  
  3635.   /**
  3636.    * The browser gives us times in terms of "time ticks" in milliseconds.
  3637.    * This function converts the tick count to a Javascript "time", which is
  3638.    * the UTC time in milliseconds.
  3639.    *
  3640.    * @param {String} timeTicks A time represented in "time ticks".
  3641.    * @return {number} The Javascript time that |timeTicks| represents.
  3642.    */
  3643.   function convertTimeTicksToTime(timeTicks) {
  3644.     return timeTickOffset + (timeTicks - 0);
  3645.   }
  3646.  
  3647.   /**
  3648.    * The browser gives us times in terms of "time ticks" in milliseconds.
  3649.    * This function converts the tick count to a Date() object.
  3650.    *
  3651.    * @param {String} timeTicks A time represented in "time ticks".
  3652.    * @return {Date} The time that |timeTicks| represents.
  3653.    */
  3654.   function convertTimeTicksToDate(timeTicks) {
  3655.     return new Date(convertTimeTicksToTime(timeTicks));
  3656.   }
  3657.  
  3658.   /**
  3659.    * Returns the current time.
  3660.    *
  3661.    * @return {number} Milliseconds since the Unix epoch.
  3662.    */
  3663.   function getCurrentTime() {
  3664.     return (new Date()).getTime();
  3665.   }
  3666.  
  3667.   /**
  3668.    * Adds an HTML representation of |date| to |parentNode|.
  3669.    *
  3670.    * @param {DomNode} parentNode The node that will contain the new node.
  3671.    * @param {Date} date The date to be displayed.
  3672.    * @return {DomNode} The new node containing the date/time.
  3673.    */
  3674.   function addNodeWithDate(parentNode, date) {
  3675.     var span = addNodeWithText(parentNode, 'span', dateToString(date));
  3676.     span.title = 't=' + date.getTime();
  3677.     return span;
  3678.   }
  3679.  
  3680.   /**
  3681.    * Returns a string representation of |date|.
  3682.    *
  3683.    * @param {Date} date The date to be represented.
  3684.    * @return {String} A string representation of |date|.
  3685.    */
  3686.   function dateToString(date) {
  3687.     var dateStr = date.getFullYear() + '-' +
  3688.                   zeroPad_(date.getMonth() + 1, 2) + '-' +
  3689.                   zeroPad_(date.getDate(), 2);
  3690.  
  3691.     var timeStr = zeroPad_(date.getHours(), 2) + ':' +
  3692.                   zeroPad_(date.getMinutes(), 2) + ':' +
  3693.                   zeroPad_(date.getSeconds(), 2) + '.' +
  3694.                   zeroPad_(date.getMilliseconds(), 3);
  3695.  
  3696.     return dateStr + ' ' + timeStr;
  3697.   }
  3698.  
  3699.   /**
  3700.    * Prefixes enough zeros to |num| so that it has length |len|.
  3701.    * @param {number} num The number to be padded.
  3702.    * @param {number} len The desired length of the returned string.
  3703.    * @return {string} The zero-padded representation of |num|.
  3704.    */
  3705.   function zeroPad_(num, len) {
  3706.     var str = num + '';
  3707.     while (str.length < len)
  3708.       str = '0' + str;
  3709.     return str;
  3710.   }
  3711.  
  3712.   return {
  3713.     setTimeTickOffset: setTimeTickOffset,
  3714.     convertTimeTicksToTime: convertTimeTicksToTime,
  3715.     convertTimeTicksToDate: convertTimeTicksToDate,
  3716.     getCurrentTime: getCurrentTime,
  3717.     addNodeWithDate: addNodeWithDate,
  3718.     dateToString: dateToString
  3719.   };
  3720. })();
  3721.  
  3722. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  3723. // Use of this source code is governed by a BSD-style license that can be
  3724. // found in the LICENSE file.
  3725.  
  3726. log_util = (function() {
  3727.   'use strict';
  3728.  
  3729.   /**
  3730.    * Creates a new log dump.  |events| is a list of all events, |polledData| is
  3731.    * an object containing the results of each poll, |tabData| is an object
  3732.    * containing data for individual tabs, |date| is the time the dump was
  3733.    * created, as a formatted string, and |privacyStripping| is whether or not
  3734.    * private information should be removed from the generated dump.
  3735.    *
  3736.    * Returns the new log dump as an object.  Resulting object may have a null
  3737.    * |numericDate|.
  3738.    *
  3739.    * TODO(eroman): Use javadoc notation for these parameters.
  3740.    *
  3741.    * Log dumps are just JSON objects containing five values:
  3742.    *
  3743.    *   |userComments| User-provided notes describing what this dump file is
  3744.    *                  about.
  3745.    *   |constants| needed to interpret the data.  This also includes some
  3746.    *               browser state information.
  3747.    *   |events| from the NetLog.
  3748.    *   |polledData| from each PollableDataHelper available on the source OS.
  3749.    *   |tabData| containing any tab-specific state that's not present in
  3750.    *             |polledData|.
  3751.    *
  3752.    * |polledData| and |tabData| may be empty objects, or may be missing data for
  3753.    * tabs not present on the OS the log is from.
  3754.    */
  3755.   function createLogDump(userComments, constants, events, polledData, tabData,
  3756.                          numericDate, privacyStripping) {
  3757.     if (privacyStripping)
  3758.       events = events.map(stripCookiesAndLoginInfo);
  3759.  
  3760.     var logDump = {
  3761.       'userComments': userComments,
  3762.       'constants': constants,
  3763.       'events': events,
  3764.       'polledData': polledData,
  3765.       'tabData': tabData
  3766.     };
  3767.  
  3768.     // Not technically client info, but it's used at the same point in the code.
  3769.     if (numericDate && constants.clientInfo) {
  3770.       constants.clientInfo.numericDate = numericDate;
  3771.     }
  3772.  
  3773.     return logDump;
  3774.   }
  3775.  
  3776.   /**
  3777.    * Returns a new log dump created using the polled data and date from the
  3778.    * |oldLogDump|.  The other parts of the log dump come from current
  3779.    * net-internals state.
  3780.    */
  3781.   function createUpdatedLogDump(userComments, oldLogDump, privacyStripping) {
  3782.     var numericDate = null;
  3783.     if (oldLogDump.constants.clientInfo &&
  3784.         oldLogDump.constants.clientInfo.numericDate) {
  3785.       numericDate = oldLogDump.constants.clientInfo.numericDate;
  3786.     }
  3787.     var logDump = createLogDump(
  3788.         userComments,
  3789.         Constants,
  3790.         EventsTracker.getInstance().getAllCapturedEvents(),
  3791.         oldLogDump.polledData,
  3792.         getTabData_(),
  3793.         numericDate,
  3794.         privacyStripping);
  3795.     return JSON.stringify(logDump, null, ' ');
  3796.   }
  3797.  
  3798.   /**
  3799.    * Creates a full log dump using |polledData| and the return value of each
  3800.    * tab's saveState function and passes it to |callback|.
  3801.    */
  3802.   function onUpdateAllCompleted(userComments, callback, privacyStripping,
  3803.                                 polledData) {
  3804.     var logDump = createLogDump(
  3805.         userComments,
  3806.         Constants,
  3807.         EventsTracker.getInstance().getAllCapturedEvents(),
  3808.         polledData,
  3809.         getTabData_(),
  3810.         timeutil.getCurrentTime(),
  3811.         privacyStripping);
  3812.     callback(JSON.stringify(logDump, null, ' '));
  3813.   }
  3814.  
  3815.   /**
  3816.    * Called to create a new log dump.  Must not be called once a dump has been
  3817.    * loaded.  Once a log dump has been created, |callback| is passed the dumped
  3818.    * text as a string.
  3819.    */
  3820.   function createLogDumpAsync(userComments, callback, privacyStripping) {
  3821.     g_browser.updateAllInfo(
  3822.         onUpdateAllCompleted.bind(null, userComments, callback,
  3823.                                   privacyStripping));
  3824.   }
  3825.  
  3826.   /**
  3827.    * Gather any tab-specific state information prior to creating a log dump.
  3828.    */
  3829.   function getTabData_() {
  3830.     var tabData = {};
  3831.     var categoryTabSwitcher = MainView.getInstance().categoryTabSwitcher();
  3832.     var tabIds = categoryTabSwitcher.getAllTabIds();
  3833.     for (var i = 0; i < tabIds.length; ++i) {
  3834.       var view = categoryTabSwitcher.findTabById(tabIds[i]).contentView;
  3835.       if (view.saveState)
  3836.         tabData[tabIds[i]] = view.saveState();
  3837.     }
  3838.   }
  3839.  
  3840.   /**
  3841.    * Loads a full log dump.  Returns a string containing a log of the load.
  3842.    * |opt_fileName| should always be given when loading from a file, instead of
  3843.    * from a log dump generated in-memory.
  3844.    * The process goes like this:
  3845.    * 1)  Load constants.  If this fails, or the version number can't be handled,
  3846.    *     abort the load.  If this step succeeds, the load cannot be aborted.
  3847.    * 2)  Clear all events.  Any event observers are informed of the clear as
  3848.    *     normal.
  3849.    * 3)  Call onLoadLogStart(polledData, tabData) for each view with an
  3850.    *     onLoadLogStart function.  This allows tabs to clear any extra state
  3851.    *     that would affect the next step.  |polledData| contains the data polled
  3852.    *     for all helpers, but |tabData| contains only the data from that
  3853.    *     specific tab.
  3854.    * 4)  Add all events from the log file.
  3855.    * 5)  Call onLoadLogFinish(polledData, tabData) for each view with an
  3856.    *     onLoadLogFinish function.  The arguments are the same as in step 3.  If
  3857.    *     there is no onLoadLogFinish function, it throws an exception, or it
  3858.    *     returns false instead of true, the data dump is assumed to contain no
  3859.    *     valid data for the tab, so the tab is hidden.  Otherwise, the tab is
  3860.    *     shown.
  3861.    */
  3862.   function loadLogDump(logDump, opt_fileName) {
  3863.     // Perform minimal validity check, and abort if it fails.
  3864.     if (typeof(logDump) != 'object')
  3865.       return 'Load failed.  Top level JSON data is not an object.';
  3866.  
  3867.     // String listing text summary of load errors, if any.
  3868.     var errorString = '';
  3869.  
  3870.     if (!areValidConstants(logDump.constants))
  3871.       errorString += 'Invalid constants object.\n';
  3872.     if (typeof(logDump.events) != 'object')
  3873.       errorString += 'NetLog events missing.\n';
  3874.     if (typeof(logDump.constants.logFormatVersion) != 'number')
  3875.       errorString += 'Invalid version number.\n';
  3876.  
  3877.     if (errorString.length > 0)
  3878.       return 'Load failed:\n\n' + errorString;
  3879.  
  3880.     if (typeof(logDump.polledData) != 'object')
  3881.       logDump.polledData = {};
  3882.     if (typeof(logDump.tabData) != 'object')
  3883.       logDump.tabData = {};
  3884.  
  3885.     if (logDump.constants.logFormatVersion != Constants.logFormatVersion) {
  3886.       return 'Unable to load different log version.' +
  3887.              ' Found ' + logDump.constants.logFormatVersion +
  3888.              ', Expected ' + Constants.logFormatVersion;
  3889.     }
  3890.  
  3891.     g_browser.receivedConstants(logDump.constants);
  3892.  
  3893.     // Check for validity of each log entry, and then add the ones that pass.
  3894.     // Since the events are kept around, and we can't just hide a single view
  3895.     // on a bad event, we have more error checking for them than other data.
  3896.     var validEvents = [];
  3897.     var numDeprecatedPassiveEvents = 0;
  3898.     for (var eventIndex = 0; eventIndex < logDump.events.length; ++eventIndex) {
  3899.       var event = logDump.events[eventIndex];
  3900.       if (typeof event == 'object' &&
  3901.           typeof event.source == 'object' &&
  3902.           typeof event.time == 'string' &&
  3903.           typeof EventTypeNames[event.type] == 'string' &&
  3904.           typeof EventSourceTypeNames[event.source.type] == 'string' &&
  3905.           getKeyWithValue(EventPhase, event.phase) != '?') {
  3906.         if (event.wasPassivelyCaptured) {
  3907.           // NOTE: Up until Chrome 18, log dumps included "passively captured"
  3908.           // events. These are no longer supported, so skip past them
  3909.           // to avoid confusing the rest of the code.
  3910.           numDeprecatedPassiveEvents++;
  3911.           continue;
  3912.         }
  3913.         validEvents.push(event);
  3914.       }
  3915.     }
  3916.  
  3917.     // Make sure the loaded log contained an export date. If not we will
  3918.     // synthesize one. This can legitimately happen for dump files created
  3919.     // via command line flag, or for older dump formats (before Chrome 17).
  3920.     if (typeof logDump.constants.clientInfo.numericDate != 'number') {
  3921.       errorString += 'The log file is missing clientInfo.numericDate.\n';
  3922.  
  3923.       if (validEvents.length > 0) {
  3924.         errorString +=
  3925.             'Synthesizing export date as time of last event captured.\n';
  3926.         var lastEvent = validEvents[validEvents.length - 1];
  3927.         ClientInfo.numericDate =
  3928.             timeutil.convertTimeTicksToDate(lastEvent.time).getTime();
  3929.       } else {
  3930.         errorString += 'Can\'t guess export date!\n';
  3931.         ClientInfo.numericDate = 0;
  3932.       }
  3933.     }
  3934.  
  3935.     // Prevent communication with the browser.  Once the constants have been
  3936.     // loaded, it's safer to continue trying to load the log, even in the case
  3937.     // of bad data.
  3938.     MainView.getInstance().onLoadLog(opt_fileName);
  3939.  
  3940.     // Delete all events.  This will also update all logObservers.
  3941.     EventsTracker.getInstance().deleteAllLogEntries();
  3942.  
  3943.     // Inform all the views that a log file is being loaded, and pass in
  3944.     // view-specific saved state, if any.
  3945.     var categoryTabSwitcher = MainView.getInstance().categoryTabSwitcher();
  3946.     var tabIds = categoryTabSwitcher.getAllTabIds();
  3947.     for (var i = 0; i < tabIds.length; ++i) {
  3948.       var view = categoryTabSwitcher.findTabById(tabIds[i]).contentView;
  3949.       view.onLoadLogStart(logDump.polledData, logDump.tabData[tabIds[i]]);
  3950.     }
  3951.     EventsTracker.getInstance().addLogEntries(validEvents);
  3952.  
  3953.     var numInvalidEvents = logDump.events.length -
  3954.         (validEvents.length + numDeprecatedPassiveEvents);
  3955.     if (numInvalidEvents > 0) {
  3956.       errorString += 'Unable to load ' + numInvalidEvents +
  3957.                      ' events, due to invalid data.\n\n';
  3958.     }
  3959.  
  3960.     if (numDeprecatedPassiveEvents > 0) {
  3961.       errorString += 'Discarded ' + numDeprecatedPassiveEvents +
  3962.           ' passively collected events. Use an older version of Chrome to' +
  3963.           ' load this dump if you want to see them.\n\n';
  3964.     }
  3965.  
  3966.     // Update all views with data from the file.  Show only those views which
  3967.     // successfully load the data.
  3968.     for (var i = 0; i < tabIds.length; ++i) {
  3969.       var view = categoryTabSwitcher.findTabById(tabIds[i]).contentView;
  3970.       var showView = false;
  3971.       // The try block eliminates the need for checking every single value
  3972.       // before trying to access it.
  3973.       try {
  3974.         if (view.onLoadLogFinish(logDump.polledData,
  3975.                                  logDump.tabData[tabIds[i]],
  3976.                                  logDump)) {
  3977.           showView = true;
  3978.         }
  3979.       } catch (error) {
  3980.       }
  3981.       categoryTabSwitcher.showTabHandleNode(tabIds[i], showView);
  3982.     }
  3983.  
  3984.     return errorString + 'Log loaded.';
  3985.   }
  3986.  
  3987.   /**
  3988.    * Loads a log dump from the string |logFileContents|, which can be either a
  3989.    * full net-internals dump, or a NetLog dump only.  Returns a string
  3990.    * containing a log of the load.
  3991.    */
  3992.   function loadLogFile(logFileContents, fileName) {
  3993.     // Try and parse the log dump as a single JSON string.  If this succeeds,
  3994.     // it's most likely a full log dump.  Otherwise, it may be a dump created by
  3995.     // --log-net-log.
  3996.     var parsedDump = null;
  3997.     try {
  3998.       parsedDump = JSON.parse(logFileContents);
  3999.     } catch (error) {
  4000.       try {
  4001.         // We may have a --log-net-log=blah log dump.  If so, remove the comma
  4002.         // after the final good entry, and add the necessary close brackets.
  4003.         var end = Math.max(logFileContents.lastIndexOf(',\n'),
  4004.                            logFileContents.lastIndexOf(',\r'));
  4005.         if (end != -1)
  4006.           parsedDump = JSON.parse(logFileContents.substring(0, end) + ']}');
  4007.       }
  4008.       catch (error2) {
  4009.       }
  4010.     }
  4011.  
  4012.     if (!parsedDump)
  4013.       return 'Unable to parse log dump as JSON file.';
  4014.     return loadLogDump(parsedDump, fileName);
  4015.   }
  4016.  
  4017.   // Exports.
  4018.   return {
  4019.     createUpdatedLogDump: createUpdatedLogDump,
  4020.     createLogDumpAsync: createLogDumpAsync,
  4021.     loadLogFile: loadLogFile
  4022.   };
  4023. })();
  4024.  
  4025. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  4026. // Use of this source code is governed by a BSD-style license that can be
  4027. // found in the LICENSE file.
  4028.  
  4029. /**
  4030.  * The status view at the top of the page when in capturing mode.
  4031.  */
  4032. var CaptureStatusView = (function() {
  4033.   'use strict';
  4034.  
  4035.   // We inherit from DivView.
  4036.   var superClass = DivView;
  4037.  
  4038.   function CaptureStatusView() {
  4039.     superClass.call(this, CaptureStatusView.MAIN_BOX_ID);
  4040.  
  4041.     $(CaptureStatusView.STOP_BUTTON_ID).onclick = switchToViewOnlyMode_;
  4042.  
  4043.     $(CaptureStatusView.RESET_BUTTON_ID).onclick =
  4044.         EventsTracker.getInstance().deleteAllLogEntries.bind(
  4045.             EventsTracker.getInstance());
  4046.     $(CaptureStatusView.CLEAR_CACHE_BUTTON_ID).onclick =
  4047.         g_browser.sendClearAllCache.bind(g_browser);
  4048.     $(CaptureStatusView.FLUSH_SOCKETS_BUTTON_ID).onclick =
  4049.         g_browser.sendFlushSocketPools.bind(g_browser);
  4050.  
  4051.     $(CaptureStatusView.TOGGLE_EXTRAS_ID).onclick =
  4052.         this.toggleExtras_.bind(this);
  4053.  
  4054.     this.capturedEventsCountBox_ =
  4055.         $(CaptureStatusView.CAPTURED_EVENTS_COUNT_ID);
  4056.     this.updateEventCounts_();
  4057.  
  4058.     EventsTracker.getInstance().addLogEntryObserver(this);
  4059.   }
  4060.  
  4061.   // IDs for special HTML elements in status_view.html
  4062.   CaptureStatusView.MAIN_BOX_ID = 'capture-status-view';
  4063.   CaptureStatusView.STOP_BUTTON_ID = 'capture-status-view-stop';
  4064.   CaptureStatusView.RESET_BUTTON_ID = 'capture-status-view-reset';
  4065.   CaptureStatusView.CLEAR_CACHE_BUTTON_ID = 'capture-status-view-clear-cache';
  4066.   CaptureStatusView.FLUSH_SOCKETS_BUTTON_ID =
  4067.       'capture-status-view-flush-sockets';
  4068.   CaptureStatusView.CAPTURED_EVENTS_COUNT_ID =
  4069.       'capture-status-view-captured-events-count';
  4070.   CaptureStatusView.TOGGLE_EXTRAS_ID = 'capture-status-view-toggle-extras';
  4071.   CaptureStatusView.EXTRAS_ID = 'capture-status-view-extras';
  4072.  
  4073.   CaptureStatusView.prototype = {
  4074.     // Inherit the superclass's methods.
  4075.     __proto__: superClass.prototype,
  4076.  
  4077.     /**
  4078.      * Called whenever new log entries have been received.
  4079.      */
  4080.     onReceivedLogEntries: function(logEntries) {
  4081.       this.updateEventCounts_();
  4082.     },
  4083.  
  4084.     /**
  4085.      * Called whenever all log events are deleted.
  4086.      */
  4087.     onAllLogEntriesDeleted: function() {
  4088.       this.updateEventCounts_();
  4089.     },
  4090.  
  4091.     /**
  4092.      * Updates the counters showing how many events have been captured.
  4093.      */
  4094.     updateEventCounts_: function() {
  4095.       this.capturedEventsCountBox_.textContent =
  4096.           EventsTracker.getInstance().getNumCapturedEvents();
  4097.     },
  4098.  
  4099.     /**
  4100.      * Toggles the visibility of the "extras" action bar.
  4101.      */
  4102.     toggleExtras_: function() {
  4103.       var toggle = $(CaptureStatusView.TOGGLE_EXTRAS_ID);
  4104.       var extras = $(CaptureStatusView.EXTRAS_ID);
  4105.  
  4106.       var isVisible = extras.style.display == '';
  4107.  
  4108.       // Toggle between the left-facing triangle and right-facing triange.
  4109.       toggle.className = isVisible ?
  4110.           'capture-status-view-rotateleft' : 'capture-status-view-rotateright';
  4111.       setNodeDisplay(extras, !isVisible);
  4112.     }
  4113.   };
  4114.  
  4115.   /**
  4116.    * Calls the corresponding function of MainView.  This is needed because we
  4117.    * can't call MainView.getInstance() in the constructor, as it hasn't been
  4118.    * created yet.
  4119.    */
  4120.   function switchToViewOnlyMode_() {
  4121.     MainView.getInstance().switchToViewOnlyMode();
  4122.   }
  4123.  
  4124.   return CaptureStatusView;
  4125. })();
  4126.  
  4127. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  4128. // Use of this source code is governed by a BSD-style license that can be
  4129. // found in the LICENSE file.
  4130.  
  4131. /**
  4132.  * The status view at the top of the page when viewing a loaded dump file.
  4133.  */
  4134. var LoadedStatusView = (function() {
  4135.   'use strict';
  4136.  
  4137.   // We inherit from DivView.
  4138.   var superClass = DivView;
  4139.  
  4140.   function LoadedStatusView() {
  4141.     superClass.call(this, LoadedStatusView.MAIN_BOX_ID);
  4142.   }
  4143.  
  4144.   LoadedStatusView.MAIN_BOX_ID = 'loaded-status-view';
  4145.   LoadedStatusView.DUMP_FILE_NAME_ID = 'loaded-status-view-dump-file-name';
  4146.  
  4147.   LoadedStatusView.prototype = {
  4148.     // Inherit the superclass's methods.
  4149.     __proto__: superClass.prototype,
  4150.  
  4151.     setFileName: function(fileName) {
  4152.       $(LoadedStatusView.DUMP_FILE_NAME_ID).innerText = fileName;
  4153.     }
  4154.   };
  4155.  
  4156.   return LoadedStatusView;
  4157. })();
  4158.  
  4159. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  4160. // Use of this source code is governed by a BSD-style license that can be
  4161. // found in the LICENSE file.
  4162.  
  4163. /**
  4164.  * The status view at the top of the page after stopping capturing.
  4165.  */
  4166. var HaltedStatusView = (function() {
  4167.   'use strict';
  4168.  
  4169.   // We inherit from DivView.
  4170.   var superClass = DivView;
  4171.  
  4172.   function HaltedStatusView() {
  4173.     superClass.call(this, HaltedStatusView.MAIN_BOX_ID);
  4174.   }
  4175.  
  4176.   HaltedStatusView.MAIN_BOX_ID = 'halted-status-view';
  4177.  
  4178.   HaltedStatusView.prototype = {
  4179.     // Inherit the superclass's methods.
  4180.     __proto__: superClass.prototype
  4181.   };
  4182.  
  4183.   return HaltedStatusView;
  4184. })();
  4185.  
  4186. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  4187. // Use of this source code is governed by a BSD-style license that can be
  4188. // found in the LICENSE file.
  4189.  
  4190. /**
  4191.  * The status view at the top of the page.  It displays what mode net-internals
  4192.  * is in (capturing, viewing only, viewing loaded log), and may have extra
  4193.  * information and actions depending on the mode.
  4194.  */
  4195. var StatusView = (function() {
  4196.   'use strict';
  4197.  
  4198.   // We inherit from View.
  4199.   var superClass = View;
  4200.  
  4201.   /**
  4202.    * Main entry point. Called once the page has loaded.
  4203.    * @constructor
  4204.    */
  4205.   function StatusView() {
  4206.     assertFirstConstructorCall(StatusView);
  4207.  
  4208.     superClass.call(this);
  4209.  
  4210.     this.subViews_ = {
  4211.       capture: new CaptureStatusView(),
  4212.       loaded: new LoadedStatusView(),
  4213.       halted: new HaltedStatusView()
  4214.     };
  4215.  
  4216.     this.activeSubViewName_ = 'capture';
  4217.  
  4218.     // Hide the non-active views.
  4219.     for (var k in this.subViews_) {
  4220.       if (k != this.activeSubViewName_)
  4221.         this.subViews_[k].show(false);
  4222.     }
  4223.   }
  4224.  
  4225.   cr.addSingletonGetter(StatusView);
  4226.  
  4227.   StatusView.prototype = {
  4228.     // Inherit the superclass's methods.
  4229.     __proto__: superClass.prototype,
  4230.  
  4231.     setGeometry: function(left, top, width, height) {
  4232.       superClass.prototype.setGeometry.call(this, left, top, width, height);
  4233.       this.getActiveSubView_().setGeometry(left, top, width, height);
  4234.     },
  4235.  
  4236.     getHeight: function() {
  4237.       return this.getActiveSubView_().getHeight();
  4238.     },
  4239.  
  4240.     show: function(isVisible) {
  4241.       superClass.prototype.show.call(this, isVisible);
  4242.       this.getActiveSubView_().show(isVisible);
  4243.     },
  4244.  
  4245.     setLayoutParent: function(view) {
  4246.       this.layoutParent_ = view;
  4247.     },
  4248.  
  4249.     /**
  4250.      * Switch the active subview.
  4251.      */
  4252.     switchToSubView: function(name) {
  4253.       if (!this.subViews_[name])
  4254.         throw 'Invalid subview name: ' + name;
  4255.  
  4256.       var prevSubView = this.getActiveSubView_();
  4257.       this.activeSubViewName_ = name;
  4258.       var newSubView = this.getActiveSubView_();
  4259.  
  4260.       prevSubView.show(false);
  4261.       newSubView.show(this.isVisible());
  4262.  
  4263.       // Since the subview's dimensions may have changed, re-trigger a layout
  4264.       // for our parent.
  4265.       var view = this.layoutParent_;
  4266.       view.setGeometry(view.getLeft(), view.getTop(),
  4267.                        view.getWidth(), view.getHeight());
  4268.  
  4269.       return newSubView;
  4270.     },
  4271.  
  4272.     getActiveSubView_: function() {
  4273.       return this.subViews_[this.activeSubViewName_];
  4274.     }
  4275.   };
  4276.  
  4277.   return StatusView;
  4278. })();
  4279.  
  4280. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  4281. // Use of this source code is governed by a BSD-style license that can be
  4282. // found in the LICENSE file.
  4283.  
  4284. /**
  4285.  * This view displays information on the host resolver:
  4286.  *
  4287.  *   - Shows the default address family.
  4288.  *   - Has a button to enable IPv6, if it is disabled.
  4289.  *   - Shows the current host cache contents.
  4290.  *   - Has a button to clear the host cache.
  4291.  *   - Shows the parameters used to construct the host cache (capacity, ttl).
  4292.  */
  4293.  
  4294. // TODO(mmenke):  Add links for each address entry to the corresponding NetLog
  4295. //                source.  This could either be done by adding NetLog source ids
  4296. //                to cache entries, or tracking sources based on their type and
  4297. //                description.  Former is simpler, latter may be useful
  4298. //                elsewhere as well.
  4299. var DnsView = (function() {
  4300.   'use strict';
  4301.  
  4302.   // We inherit from DivView.
  4303.   var superClass = DivView;
  4304.  
  4305.   /**
  4306.    *  @constructor
  4307.    */
  4308.   function DnsView() {
  4309.     assertFirstConstructorCall(DnsView);
  4310.  
  4311.     // Call superclass's constructor.
  4312.     superClass.call(this, DnsView.MAIN_BOX_ID);
  4313.  
  4314.     $(DnsView.ENABLE_IPV6_BUTTON_ID).onclick =
  4315.         g_browser.enableIPv6.bind(g_browser);
  4316.     $(DnsView.IPV6_PROBE_BUTTON_ID).onclick = this.runIPv6Probe_.bind(this);
  4317.     $(DnsView.CLEAR_CACHE_BUTTON_ID).onclick =
  4318.         g_browser.sendClearHostResolverCache.bind(g_browser);
  4319.  
  4320.     // Used to track IPv6 probes.
  4321.     EventsTracker.getInstance().addLogEntryObserver(this);
  4322.     // ID of most recently started IPv6 probe job.  Once the job completes,
  4323.     // set back to -1.
  4324.     this.ipv6ProbeJobSourceId_ = -1;
  4325.  
  4326.     // Register to receive changes to the host resolver info.
  4327.     g_browser.addHostResolverInfoObserver(this, false);
  4328.   }
  4329.  
  4330.   // ID for special HTML element in category_tabs.html
  4331.   DnsView.TAB_HANDLE_ID = 'tab-handle-dns';
  4332.  
  4333.   // IDs for special HTML elements in dns_view.html
  4334.   DnsView.MAIN_BOX_ID = 'dns-view-tab-content';
  4335.   DnsView.DEFAULT_FAMILY_SPAN_ID = 'dns-view-default-family';
  4336.   DnsView.IPV6_DISABLED_SPAN_ID = 'dns-view-ipv6-disabled';
  4337.   DnsView.ENABLE_IPV6_BUTTON_ID = 'dns-view-enable-ipv6';
  4338.  
  4339.   DnsView.IPV6_PROBE_RUNNING_SPAN_ID = 'dns-view-ipv6-probe-running';
  4340.   DnsView.IPV6_PROBE_COMPLETE_SPAN_ID = 'dns-view-ipv6-probe-complete';
  4341.   DnsView.IPV6_PROBE_BUTTON_ID = 'dns-view-run-ipv6-probe';
  4342.  
  4343.   DnsView.INTERNAL_DNS_ENABLED_SPAN_ID = 'dns-view-internal-dns-enabled';
  4344.   DnsView.INTERNAL_DNS_INVALID_CONFIG_SPAN_ID =
  4345.       'dns-view-internal-dns-invalid-config';
  4346.   DnsView.INTERNAL_DNS_CONFIG_TBODY_ID = 'dns-view-internal-dns-config-tbody';
  4347.  
  4348.   DnsView.CLEAR_CACHE_BUTTON_ID = 'dns-view-clear-cache';
  4349.   DnsView.CAPACITY_SPAN_ID = 'dns-view-cache-capacity';
  4350.  
  4351.   DnsView.ACTIVE_SPAN_ID = 'dns-view-cache-active';
  4352.   DnsView.EXPIRED_SPAN_ID = 'dns-view-cache-expired';
  4353.   DnsView.CACHE_TBODY_ID = 'dns-view-cache-tbody';
  4354.  
  4355.   cr.addSingletonGetter(DnsView);
  4356.  
  4357.   DnsView.prototype = {
  4358.     // Inherit the superclass's methods.
  4359.     __proto__: superClass.prototype,
  4360.  
  4361.     onLoadLogStart: function(polledData, tabData, logDump) {
  4362.       // Clear information on whether or not an IPv6 probe is running.  Needs
  4363.       // to be done before loading the events.
  4364.       this.setIPv6ProbeJobLookupRunning_(false, -1);
  4365.     },
  4366.  
  4367.     onLoadLogFinish: function(data) {
  4368.       return this.onHostResolverInfoChanged(data.hostResolverInfo);
  4369.     },
  4370.  
  4371.     onHostResolverInfoChanged: function(hostResolverInfo) {
  4372.       // Clear the existing values.
  4373.       $(DnsView.DEFAULT_FAMILY_SPAN_ID).innerHTML = '';
  4374.       $(DnsView.CAPACITY_SPAN_ID).innerHTML = '';
  4375.       $(DnsView.CACHE_TBODY_ID).innerHTML = '';
  4376.       $(DnsView.ACTIVE_SPAN_ID).innerHTML = '0';
  4377.       $(DnsView.EXPIRED_SPAN_ID).innerHTML = '0';
  4378.  
  4379.       // Update fields containing async DNS configuration information.
  4380.       displayAsyncDnsConfig_(hostResolverInfo);
  4381.  
  4382.       // No info.
  4383.       if (!hostResolverInfo || !hostResolverInfo.cache)
  4384.         return false;
  4385.  
  4386.       var family = hostResolverInfo.default_address_family;
  4387.       addTextNode($(DnsView.DEFAULT_FAMILY_SPAN_ID),
  4388.                   addressFamilyToString(family));
  4389.  
  4390.       var ipv6Disabled = (family == AddressFamily.ADDRESS_FAMILY_IPV4);
  4391.       setNodeDisplay($(DnsView.IPV6_DISABLED_SPAN_ID), ipv6Disabled);
  4392.  
  4393.       // Fill in the basic cache information.
  4394.       var hostResolverCache = hostResolverInfo.cache;
  4395.       $(DnsView.CAPACITY_SPAN_ID).innerText = hostResolverCache.capacity;
  4396.  
  4397.       var expiredEntries = 0;
  4398.       // Date the cache was logged.  This will be either now, when actively
  4399.       // logging data, or the date the log dump was created.
  4400.       var logDate;
  4401.       if (MainView.isViewingLoadedLog()) {
  4402.         logDate = new Date(ClientInfo.numericDate);
  4403.       } else {
  4404.         logDate = new Date();
  4405.       }
  4406.  
  4407.       // Fill in the cache contents table.
  4408.       for (var i = 0; i < hostResolverCache.entries.length; ++i) {
  4409.         var e = hostResolverCache.entries[i];
  4410.         var tr = addNode($(DnsView.CACHE_TBODY_ID), 'tr');
  4411.  
  4412.         var hostnameCell = addNode(tr, 'td');
  4413.         addTextNode(hostnameCell, e.hostname);
  4414.  
  4415.         var familyCell = addNode(tr, 'td');
  4416.         addTextNode(familyCell,
  4417.                     addressFamilyToString(e.address_family));
  4418.  
  4419.         var addressesCell = addNode(tr, 'td');
  4420.  
  4421.         if (e.error != undefined) {
  4422.           var errorText =
  4423.               e.error + ' (' + netErrorToString(e.error) + ')';
  4424.           var errorNode = addTextNode(addressesCell, 'error: ' + errorText);
  4425.           addressesCell.classList.add('warning-text');
  4426.         } else {
  4427.           addListToNode_(addNode(addressesCell, 'div'), e.addresses);
  4428.         }
  4429.  
  4430.         var expiresDate = timeutil.convertTimeTicksToDate(e.expiration);
  4431.         var expiresCell = addNode(tr, 'td');
  4432.         timeutil.addNodeWithDate(expiresCell, expiresDate);
  4433.         if (logDate > timeutil.convertTimeTicksToDate(e.expiration)) {
  4434.           ++expiredEntries;
  4435.           var expiredSpan = addNode(expiresCell, 'span');
  4436.           expiredSpan.classList.add('warning-text');
  4437.           addTextNode(expiredSpan, ' [Expired]');
  4438.         }
  4439.       }
  4440.  
  4441.       $(DnsView.ACTIVE_SPAN_ID).innerText =
  4442.           hostResolverCache.entries.length - expiredEntries;
  4443.       $(DnsView.EXPIRED_SPAN_ID).innerText = expiredEntries;
  4444.       return true;
  4445.     },
  4446.  
  4447.     /**
  4448.      * Must be called whenever an IPv6 probe job starts or stops running.
  4449.      * @param {bool} running True if a probe job is running.
  4450.      * @param {sourceId} sourceId Source ID of the running probe job, if there
  4451.      *     is one.  -1 if |running| is false or we don't yet have the ID.
  4452.      */
  4453.     setIPv6ProbeJobLookupRunning_: function(running, sourceId) {
  4454.       setNodeDisplay($(DnsView.IPV6_PROBE_RUNNING_SPAN_ID), running);
  4455.       setNodeDisplay($(DnsView.IPV6_PROBE_COMPLETE_SPAN_ID), !running);
  4456.       this.ipv6ProbeJobSourceId_ = sourceId;
  4457.     },
  4458.  
  4459.     /**
  4460.      * Triggers a new IPv6 probe and displays the probe running message.
  4461.      */
  4462.     runIPv6Probe_: function() {
  4463.       // Since there's no source ID yet, have to just use -1.  We'll get the
  4464.       // ID when we see the start event for the probe.
  4465.       this.setIPv6ProbeJobLookupRunning_(true, -1);
  4466.       g_browser.sendRunIPv6Probe();
  4467.     },
  4468.  
  4469.     onReceivedLogEntries: function(logEntries) {
  4470.       for (var i = 0; i < logEntries.length; ++i) {
  4471.         if (logEntries[i].source.type != EventSourceType.IPV6_PROBE_JOB ||
  4472.             logEntries[i].type != EventType.IPV6_PROBE_RUNNING) {
  4473.           continue;
  4474.         }
  4475.  
  4476.         // For IPV6_PROBE_JOB events, update the display depending on whether or
  4477.         // not a probe job is running.  Only track the most recently started
  4478.         // probe job, as it will cancel any older jobs.
  4479.         if (logEntries[i].phase == EventPhase.PHASE_BEGIN) {
  4480.           this.setIPv6ProbeJobLookupRunning_(true, logEntries[i].source.id);
  4481.         } else if (logEntries[i].source.id == this.ipv6ProbeJobSourceId_) {
  4482.           this.setIPv6ProbeJobLookupRunning_(false, -1);
  4483.           g_browser.sendGetHostResolverInfo();
  4484.         }
  4485.       }
  4486.     },
  4487.  
  4488.     /**
  4489.      * Since the only thing that matters is the source ID of the active probe
  4490.      * job, which clearing events doesn't change, do nothing.
  4491.      */
  4492.     onAllLogEntriesDeleted: function() {
  4493.     },
  4494.   };
  4495.  
  4496.   /**
  4497.    * Displays information corresponding to the current async DNS configuration.
  4498.    * @param {Object} hostResolverInfo The host resolver information.
  4499.    */
  4500.   function displayAsyncDnsConfig_(hostResolverInfo) {
  4501.     // Clear the table.
  4502.     $(DnsView.INTERNAL_DNS_CONFIG_TBODY_ID).innerHTML = '';
  4503.  
  4504.     // Figure out if the internal DNS resolver is disabled or has no valid
  4505.     // configuration information, and update display accordingly.
  4506.     var enabled = hostResolverInfo &&
  4507.                   hostResolverInfo.dns_config !== undefined;
  4508.     var noConfig = enabled &&
  4509.                    hostResolverInfo.dns_config.nameservers === undefined;
  4510.     $(DnsView.INTERNAL_DNS_ENABLED_SPAN_ID).innerText = enabled;
  4511.     setNodeDisplay($(DnsView.INTERNAL_DNS_INVALID_CONFIG_SPAN_ID), noConfig);
  4512.  
  4513.     // If the internal DNS resolver is disabled or has no valid configuration,
  4514.     // we're done.
  4515.     if (!enabled || noConfig)
  4516.       return;
  4517.  
  4518.     var dnsConfig = hostResolverInfo.dns_config;
  4519.  
  4520.     // Display nameservers first.
  4521.     var nameserverRow = addNode($(DnsView.INTERNAL_DNS_CONFIG_TBODY_ID), 'tr');
  4522.     addNodeWithText(nameserverRow, 'th', 'nameservers');
  4523.     addListToNode_(addNode(nameserverRow, 'td'), dnsConfig.nameservers);
  4524.  
  4525.     // Add everything else in |dnsConfig| to the table.
  4526.     for (var key in dnsConfig) {
  4527.       if (key == 'nameservers')
  4528.         continue;
  4529.       var tr = addNode($(DnsView.INTERNAL_DNS_CONFIG_TBODY_ID), 'tr');
  4530.       addNodeWithText(tr, 'th', key);
  4531.       var td = addNode(tr, 'td');
  4532.  
  4533.       // For lists, display each list entry on a separate line.
  4534.       if (typeof dnsConfig[key] == 'object' &&
  4535.           dnsConfig[key].constructor == Array) {
  4536.         addListToNode_(td, dnsConfig[key]);
  4537.         continue;
  4538.       }
  4539.  
  4540.       addTextNode(td, dnsConfig[key]);
  4541.     }
  4542.   }
  4543.  
  4544.   /**
  4545.    * Takes a last of strings and adds them all to a DOM node, displaying them
  4546.    * on separate lines.
  4547.    * @param {DomNode} node The parent node.
  4548.    * @param {Array.<String>} list List of strings to add to the node.
  4549.    */
  4550.   function addListToNode_(node, list) {
  4551.     for (var i = 0; i < list.length; ++i)
  4552.       addNodeWithText(node, 'div', list[i]);
  4553.   }
  4554.  
  4555.   return DnsView;
  4556. })();
  4557.  
  4558. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  4559. // Use of this source code is governed by a BSD-style license that can be
  4560. // found in the LICENSE file.
  4561.  
  4562. var SourceRow = (function() {
  4563.   'use strict';
  4564.  
  4565.   /**
  4566.    * A SourceRow represents the row corresponding to a single SourceEntry
  4567.    * displayed by the EventsView.
  4568.    *
  4569.    * @constructor
  4570.    */
  4571.   function SourceRow(parentView, sourceEntry) {
  4572.     this.parentView_ = parentView;
  4573.  
  4574.     this.sourceEntry_ = sourceEntry;
  4575.     this.isSelected_ = false;
  4576.     this.isMatchedByFilter_ = false;
  4577.  
  4578.     // Used to set CSS class for display.  Must only be modified by calling
  4579.     // corresponding set functions.
  4580.     this.isSelected_ = false;
  4581.     this.isMouseOver_ = false;
  4582.  
  4583.     // Mirror sourceEntry's values, so we only update the DOM when necessary.
  4584.     this.isError_ = sourceEntry.isError();
  4585.     this.isInactive_ = sourceEntry.isInactive();
  4586.     this.description_ = sourceEntry.getDescription();
  4587.  
  4588.     this.createRow_();
  4589.     this.onSourceUpdated();
  4590.   }
  4591.  
  4592.   SourceRow.prototype = {
  4593.     createRow_: function() {
  4594.       // Create a row.
  4595.       var tr = addNode(this.parentView_.tableBody_, 'tr');
  4596.       tr._id = this.getSourceId();
  4597.       tr.style.display = 'none';
  4598.       this.row_ = tr;
  4599.  
  4600.       var selectionCol = addNode(tr, 'td');
  4601.       var checkbox = addNode(selectionCol, 'input');
  4602.       selectionCol.style.borderLeft = '0';
  4603.       checkbox.type = 'checkbox';
  4604.  
  4605.       var idCell = addNode(tr, 'td');
  4606.       idCell.style.textAlign = 'right';
  4607.  
  4608.       var typeCell = addNode(tr, 'td');
  4609.       var descriptionCell = addNode(tr, 'td');
  4610.       this.descriptionCell_ = descriptionCell;
  4611.  
  4612.       // Connect listeners.
  4613.       checkbox.onchange = this.onCheckboxToggled_.bind(this);
  4614.  
  4615.       var onclick = this.onClicked_.bind(this);
  4616.       idCell.onclick = onclick;
  4617.       typeCell.onclick = onclick;
  4618.       descriptionCell.onclick = onclick;
  4619.  
  4620.       tr.onmouseover = this.onMouseover_.bind(this);
  4621.       tr.onmouseout = this.onMouseout_.bind(this);
  4622.  
  4623.       // Set the cell values to match this source's data.
  4624.       if (this.getSourceId() >= 0) {
  4625.         addTextNode(idCell, this.getSourceId());
  4626.       } else {
  4627.         addTextNode(idCell, '-');
  4628.       }
  4629.       var sourceTypeString = this.sourceEntry_.getSourceTypeString();
  4630.       addTextNode(typeCell, sourceTypeString);
  4631.       this.updateDescription_();
  4632.  
  4633.       // Add a CSS classname specific to this source type (so CSS can specify
  4634.       // different stylings for different types).
  4635.       var sourceTypeClass = sourceTypeString.toLowerCase().replace(/_/g, '-');
  4636.       this.row_.classList.add('source-' + sourceTypeClass);
  4637.  
  4638.       this.updateClass_();
  4639.     },
  4640.  
  4641.     onSourceUpdated: function() {
  4642.       if (this.sourceEntry_.isInactive() != this.isInactive_ ||
  4643.           this.sourceEntry_.isError() != this.isError_) {
  4644.         this.updateClass_();
  4645.       }
  4646.  
  4647.       if (this.description_ != this.sourceEntry_.getDescription())
  4648.         this.updateDescription_();
  4649.  
  4650.       // Update filters.
  4651.       var matchesFilter = this.matchesFilter(this.parentView_.currentFilter_);
  4652.       this.setIsMatchedByFilter(matchesFilter);
  4653.     },
  4654.  
  4655.     /**
  4656.      * Changes |row_|'s class based on currently set flags.  Clears any previous
  4657.      * class set by this method.  This method is needed so that some styles
  4658.      * override others.
  4659.      */
  4660.     updateClass_: function() {
  4661.       this.isInactive_ = this.sourceEntry_.isInactive();
  4662.       this.isError_ = this.sourceEntry_.isError();
  4663.  
  4664.       // Each element of this list contains a property of |this| and the
  4665.       // corresponding class name to set if that property is true.  Entries
  4666.       // earlier in the list take precedence.
  4667.       var propertyNames = [
  4668.         ['isSelected_', 'selected'],
  4669.         ['isMouseOver_', 'mouseover'],
  4670.         ['isError_', 'error'],
  4671.         ['isInactive_', 'inactive'],
  4672.       ];
  4673.  
  4674.       // Loop through |propertyNames| in order, checking if each property
  4675.       // is true.  For the first such property found, if any, add the
  4676.       // corresponding class to the SourceEntry's row.  Remove classes
  4677.       // that correspond to any other property.
  4678.       var noStyleSet = true;
  4679.       for (var i = 0; i < propertyNames.length; ++i) {
  4680.         var setStyle = noStyleSet && this[propertyNames[i][0]];
  4681.         if (setStyle) {
  4682.           this.row_.classList.add(propertyNames[i][1]);
  4683.           noStyleSet = false;
  4684.         } else {
  4685.           this.row_.classList.remove(propertyNames[i][1]);
  4686.         }
  4687.       }
  4688.     },
  4689.  
  4690.     getSourceEntry: function() {
  4691.       return this.sourceEntry_;
  4692.     },
  4693.  
  4694.     setIsMatchedByFilter: function(isMatchedByFilter) {
  4695.       if (this.isMatchedByFilter() == isMatchedByFilter)
  4696.         return;  // No change.
  4697.  
  4698.       this.isMatchedByFilter_ = isMatchedByFilter;
  4699.  
  4700.       this.setFilterStyles(isMatchedByFilter);
  4701.  
  4702.       if (isMatchedByFilter) {
  4703.         this.parentView_.incrementPostfilterCount(1);
  4704.       } else {
  4705.         this.parentView_.incrementPostfilterCount(-1);
  4706.         // If we are filtering an entry away, make sure it is no longer
  4707.         // part of the selection.
  4708.         this.setSelected(false);
  4709.       }
  4710.     },
  4711.  
  4712.     isMatchedByFilter: function() {
  4713.       return this.isMatchedByFilter_;
  4714.     },
  4715.  
  4716.     setFilterStyles: function(isMatchedByFilter) {
  4717.       // Hide rows which have been filtered away.
  4718.       if (isMatchedByFilter) {
  4719.         this.row_.style.display = '';
  4720.       } else {
  4721.         this.row_.style.display = 'none';
  4722.       }
  4723.     },
  4724.  
  4725.     matchesFilter: function(filter) {
  4726.       if (filter.isActive && this.isInactive_)
  4727.         return false;
  4728.       if (filter.isInactive && !this.isInactive_)
  4729.         return false;
  4730.       if (filter.isError && !this.isError_)
  4731.         return false;
  4732.       if (filter.isNotError && this.isError_)
  4733.         return false;
  4734.  
  4735.       // Check source type, if needed.
  4736.       if (filter.type) {
  4737.         var i;
  4738.         var sourceType = this.sourceEntry_.getSourceTypeString().toLowerCase();
  4739.         for (i = 0; i < filter.type.length; ++i) {
  4740.           if (sourceType.search(filter.type[i]) != -1)
  4741.             break;
  4742.         }
  4743.         if (i == filter.type.length)
  4744.           return false;
  4745.       }
  4746.  
  4747.       // Check source ID, if needed.
  4748.       if (filter.id) {
  4749.         if (filter.id.indexOf(this.getSourceId() + '') == -1)
  4750.           return false;
  4751.       }
  4752.  
  4753.       if (filter.text == '')
  4754.         return true;
  4755.  
  4756.       // The description is not always contained in one of the log entries.
  4757.       if (this.description_.toLowerCase().indexOf(filter.text) != -1)
  4758.         return true;
  4759.  
  4760.       // Allow specifying source types by name.
  4761.       var sourceType = this.sourceEntry_.getSourceTypeString();
  4762.       if (sourceType.toLowerCase().indexOf(filter.text) != -1)
  4763.         return true;
  4764.  
  4765.       return searchLogEntriesForText(
  4766.           filter.text,
  4767.           this.sourceEntry_.getLogEntries(),
  4768.           SourceTracker.getInstance().getPrivacyStripping());
  4769.     },
  4770.  
  4771.     isSelected: function() {
  4772.       return this.isSelected_;
  4773.     },
  4774.  
  4775.     setSelected: function(isSelected) {
  4776.       if (isSelected == this.isSelected())
  4777.         return;
  4778.  
  4779.       this.isSelected_ = isSelected;
  4780.  
  4781.       this.setSelectedStyles(isSelected);
  4782.       this.parentView_.modifySelectionArray(this.getSourceId(), isSelected);
  4783.       this.parentView_.onSelectionChanged();
  4784.     },
  4785.  
  4786.     setSelectedStyles: function(isSelected) {
  4787.       this.isSelected_ = isSelected;
  4788.       this.getSelectionCheckbox().checked = isSelected;
  4789.       this.updateClass_();
  4790.     },
  4791.  
  4792.     setMouseoverStyle: function(isMouseOver) {
  4793.       this.isMouseOver_ = isMouseOver;
  4794.       this.updateClass_();
  4795.     },
  4796.  
  4797.     onClicked_: function() {
  4798.       this.parentView_.clearSelection();
  4799.       this.setSelected(true);
  4800.       if (this.isSelected())
  4801.         this.parentView_.scrollToSourceId(this.getSourceId());
  4802.     },
  4803.  
  4804.     onMouseover_: function() {
  4805.       this.setMouseoverStyle(true);
  4806.     },
  4807.  
  4808.     onMouseout_: function() {
  4809.       this.setMouseoverStyle(false);
  4810.     },
  4811.  
  4812.     updateDescription_: function() {
  4813.       this.description_ = this.sourceEntry_.getDescription();
  4814.       this.descriptionCell_.innerHTML = '';
  4815.       addTextNode(this.descriptionCell_, this.description_);
  4816.     },
  4817.  
  4818.     onCheckboxToggled_: function() {
  4819.       this.setSelected(this.getSelectionCheckbox().checked);
  4820.       if (this.isSelected())
  4821.         this.parentView_.scrollToSourceId(this.getSourceId());
  4822.     },
  4823.  
  4824.     getSelectionCheckbox: function() {
  4825.       return this.row_.childNodes[0].firstChild;
  4826.     },
  4827.  
  4828.     getSourceId: function() {
  4829.       return this.sourceEntry_.getSourceId();
  4830.     },
  4831.  
  4832.     /**
  4833.      * Returns source ID of the entry whose row is currently above this one's.
  4834.      * Returns null if no such node exists.
  4835.      */
  4836.     getPreviousNodeSourceId: function() {
  4837.       var prevNode = this.row_.previousSibling;
  4838.       if (prevNode == null)
  4839.         return null;
  4840.       return prevNode._id;
  4841.     },
  4842.  
  4843.     /**
  4844.      * Returns source ID of the entry whose row is currently below this one's.
  4845.      * Returns null if no such node exists.
  4846.      */
  4847.     getNextNodeSourceId: function() {
  4848.       var nextNode = this.row_.nextSibling;
  4849.       if (nextNode == null)
  4850.         return null;
  4851.       return nextNode._id;
  4852.     },
  4853.  
  4854.     /**
  4855.      * Moves current object's row before |entry|'s row.
  4856.      */
  4857.     moveBefore: function(entry) {
  4858.       this.row_.parentNode.insertBefore(this.row_, entry.row_);
  4859.     },
  4860.  
  4861.     /**
  4862.      * Moves current object's row after |entry|'s row.
  4863.      */
  4864.     moveAfter: function(entry) {
  4865.       this.row_.parentNode.insertBefore(this.row_, entry.row_.nextSibling);
  4866.     }
  4867.   };
  4868.  
  4869.   return SourceRow;
  4870. })();
  4871.  
  4872. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  4873. // Use of this source code is governed by a BSD-style license that can be
  4874. // found in the LICENSE file.
  4875.  
  4876. /**
  4877.  * EventsView displays a filtered list of all events sharing a source, and
  4878.  * a details pane for the selected sources.
  4879.  *
  4880.  *  +----------------------++----------------+
  4881.  *  |      filter box      ||                |
  4882.  *  +----------------------+|                |
  4883.  *  |                      ||                |
  4884.  *  |                      ||                |
  4885.  *  |                      ||                |
  4886.  *  |                      ||                |
  4887.  *  |     source list      ||    details     |
  4888.  *  |                      ||    view        |
  4889.  *  |                      ||                |
  4890.  *  |                      ||                |
  4891.  *  |                      ||                |
  4892.  *  |                      ||                |
  4893.  *  |                      ||                |
  4894.  *  |                      ||                |
  4895.  *  +----------------------++----------------+
  4896.  */
  4897. var EventsView = (function() {
  4898.   'use strict';
  4899.  
  4900.   // How soon after updating the filter list the counter should be updated.
  4901.   var REPAINT_FILTER_COUNTER_TIMEOUT_MS = 0;
  4902.  
  4903.   // We inherit from View.
  4904.   var superClass = View;
  4905.  
  4906.   /*
  4907.    * @constructor
  4908.    */
  4909.   function EventsView() {
  4910.     assertFirstConstructorCall(EventsView);
  4911.  
  4912.     // Call superclass's constructor.
  4913.     superClass.call(this);
  4914.  
  4915.     // Initialize the sub-views.
  4916.     var leftPane = new VerticalSplitView(new DivView(EventsView.TOPBAR_ID),
  4917.                                          new DivView(EventsView.LIST_BOX_ID));
  4918.  
  4919.     this.detailsView_ = new DetailsView(EventsView.DETAILS_LOG_BOX_ID);
  4920.  
  4921.     this.splitterView_ = new ResizableVerticalSplitView(
  4922.         leftPane, this.detailsView_, new DivView(EventsView.SIZER_ID));
  4923.  
  4924.     SourceTracker.getInstance().addSourceEntryObserver(this);
  4925.  
  4926.     this.tableBody_ = $(EventsView.TBODY_ID);
  4927.  
  4928.     this.filterInput_ = $(EventsView.FILTER_INPUT_ID);
  4929.     this.filterCount_ = $(EventsView.FILTER_COUNT_ID);
  4930.  
  4931.     this.filterInput_.addEventListener('search',
  4932.         this.onFilterTextChanged_.bind(this), true);
  4933.  
  4934.     $(EventsView.SELECT_ALL_ID).addEventListener(
  4935.         'click', this.selectAll_.bind(this), true);
  4936.  
  4937.     $(EventsView.SORT_BY_ID_ID).addEventListener(
  4938.         'click', this.sortById_.bind(this), true);
  4939.  
  4940.     $(EventsView.SORT_BY_SOURCE_TYPE_ID).addEventListener(
  4941.         'click', this.sortBySourceType_.bind(this), true);
  4942.  
  4943.     $(EventsView.SORT_BY_DESCRIPTION_ID).addEventListener(
  4944.         'click', this.sortByDescription_.bind(this), true);
  4945.  
  4946.     new MouseOverHelp(EventsView.FILTER_HELP_ID,
  4947.                       EventsView.FILTER_HELP_HOVER_ID);
  4948.  
  4949.     // Sets sort order and filter.
  4950.     this.setFilter_('');
  4951.  
  4952.     this.initializeSourceList_();
  4953.   }
  4954.  
  4955.   // ID for special HTML element in category_tabs.html
  4956.   EventsView.TAB_HANDLE_ID = 'tab-handle-events';
  4957.  
  4958.   // IDs for special HTML elements in events_view.html
  4959.   EventsView.TBODY_ID = 'events-view-source-list-tbody';
  4960.   EventsView.FILTER_INPUT_ID = 'events-view-filter-input';
  4961.   EventsView.FILTER_COUNT_ID = 'events-view-filter-count';
  4962.   EventsView.FILTER_HELP_ID = 'events-view-filter-help';
  4963.   EventsView.FILTER_HELP_HOVER_ID = 'events-view-filter-help-hover';
  4964.   EventsView.SELECT_ALL_ID = 'events-view-select-all';
  4965.   EventsView.SORT_BY_ID_ID = 'events-view-sort-by-id';
  4966.   EventsView.SORT_BY_SOURCE_TYPE_ID = 'events-view-sort-by-source';
  4967.   EventsView.SORT_BY_DESCRIPTION_ID = 'events-view-sort-by-description';
  4968.   EventsView.DETAILS_LOG_BOX_ID = 'events-view-details-log-box';
  4969.   EventsView.TOPBAR_ID = 'events-view-filter-box';
  4970.   EventsView.LIST_BOX_ID = 'events-view-source-list';
  4971.   EventsView.SIZER_ID = 'events-view-splitter-box';
  4972.  
  4973.   cr.addSingletonGetter(EventsView);
  4974.  
  4975.   EventsView.prototype = {
  4976.     // Inherit the superclass's methods.
  4977.     __proto__: superClass.prototype,
  4978.  
  4979.     /**
  4980.      * Initializes the list of source entries.  If source entries are already,
  4981.      * being displayed, removes them all in the process.
  4982.      */
  4983.     initializeSourceList_: function() {
  4984.       this.currentSelectedRows_ = [];
  4985.       this.sourceIdToRowMap_ = {};
  4986.       this.tableBody_.innerHTML = '';
  4987.       this.numPrefilter_ = 0;
  4988.       this.numPostfilter_ = 0;
  4989.       this.invalidateFilterCounter_();
  4990.       this.invalidateDetailsView_();
  4991.     },
  4992.  
  4993.     setGeometry: function(left, top, width, height) {
  4994.       superClass.prototype.setGeometry.call(this, left, top, width, height);
  4995.       this.splitterView_.setGeometry(left, top, width, height);
  4996.     },
  4997.  
  4998.     show: function(isVisible) {
  4999.       superClass.prototype.show.call(this, isVisible);
  5000.       this.splitterView_.show(isVisible);
  5001.     },
  5002.  
  5003.     getFilterText_: function() {
  5004.       return this.filterInput_.value;
  5005.     },
  5006.  
  5007.     setFilterText_: function(filterText) {
  5008.       this.filterInput_.value = filterText;
  5009.       this.onFilterTextChanged_();
  5010.     },
  5011.  
  5012.     onFilterTextChanged_: function() {
  5013.       this.setFilter_(this.getFilterText_());
  5014.     },
  5015.  
  5016.     /**
  5017.      * Updates text in the details view when privacy stripping is toggled.
  5018.      */
  5019.     onPrivacyStrippingChanged: function() {
  5020.       this.invalidateDetailsView_();
  5021.     },
  5022.  
  5023.     comparisonFuncWithReversing_: function(a, b) {
  5024.       var result = this.comparisonFunction_(a, b);
  5025.       if (this.doSortBackwards_)
  5026.         result *= -1;
  5027.       return result;
  5028.     },
  5029.  
  5030.     sort_: function() {
  5031.       var sourceEntries = [];
  5032.       for (var id in this.sourceIdToRowMap_) {
  5033.         sourceEntries.push(this.sourceIdToRowMap_[id].getSourceEntry());
  5034.       }
  5035.       sourceEntries.sort(this.comparisonFuncWithReversing_.bind(this));
  5036.  
  5037.       // Reposition source rows from back to front.
  5038.       for (var i = sourceEntries.length - 2; i >= 0; --i) {
  5039.         var sourceRow = this.sourceIdToRowMap_[sourceEntries[i].getSourceId()];
  5040.         var nextSourceId = sourceEntries[i + 1].getSourceId();
  5041.         if (sourceRow.getNextNodeSourceId() != nextSourceId) {
  5042.           var nextSourceRow = this.sourceIdToRowMap_[nextSourceId];
  5043.           sourceRow.moveBefore(nextSourceRow);
  5044.         }
  5045.       }
  5046.     },
  5047.  
  5048.     /**
  5049.      * Looks for the first occurence of |directive|:parameter in |sourceText|.
  5050.      * Parameter can be an empty string.
  5051.      *
  5052.      * On success, returns an object with two fields:
  5053.      *   |remainingText| - |sourceText| with |directive|:parameter removed,
  5054.                            and excess whitespace deleted.
  5055.      *   |parameter| - the parameter itself.
  5056.      *
  5057.      * On failure, returns null.
  5058.      */
  5059.     parseDirective_: function(sourceText, directive) {
  5060.       // Adding a leading space allows a single regexp to be used, regardless of
  5061.       // whether or not the directive is at the start of the string.
  5062.       sourceText = ' ' + sourceText;
  5063.       var regExp = new RegExp('\\s+' + directive + ':(\\S*)\\s*', 'i');
  5064.       var matchInfo = regExp.exec(sourceText);
  5065.       if (matchInfo == null)
  5066.         return null;
  5067.  
  5068.       return {'remainingText': sourceText.replace(regExp, ' ').trim(),
  5069.               'parameter': matchInfo[1]};
  5070.     },
  5071.  
  5072.     /**
  5073.      * Just like parseDirective_, except can optionally be a '-' before or
  5074.      * the parameter, to negate it.  Before is more natural, after
  5075.      * allows more convenient toggling.
  5076.      *
  5077.      * Returned value has the additional field |isNegated|, and a leading
  5078.      * '-' will be removed from |parameter|, if present.
  5079.      */
  5080.     parseNegatableDirective_: function(sourceText, directive) {
  5081.       var matchInfo = this.parseDirective_(sourceText, directive);
  5082.       if (matchInfo == null)
  5083.         return null;
  5084.  
  5085.       // Remove any leading or trailing '-' from the directive.
  5086.       var negationInfo = /^(-?)(\S*?)$/.exec(matchInfo.parameter);
  5087.       matchInfo.parameter = negationInfo[2];
  5088.       matchInfo.isNegated = (negationInfo[1] == '-');
  5089.       return matchInfo;
  5090.     },
  5091.  
  5092.     /**
  5093.      * Parse any "sort:" directives, and update |comparisonFunction_| and
  5094.      * |doSortBackwards_|as needed.  Note only the last valid sort directive
  5095.      * is used.
  5096.      *
  5097.      * Returns |filterText| with all sort directives removed, including
  5098.      * invalid ones.
  5099.      */
  5100.     parseSortDirectives_: function(filterText) {
  5101.       this.comparisonFunction_ = compareSourceId;
  5102.       this.doSortBackwards_ = false;
  5103.  
  5104.       while (true) {
  5105.         var sortInfo = this.parseNegatableDirective_(filterText, 'sort');
  5106.         if (sortInfo == null)
  5107.           break;
  5108.         var comparisonName = sortInfo.parameter.toLowerCase();
  5109.         if (COMPARISON_FUNCTION_TABLE[comparisonName] != null) {
  5110.           this.comparisonFunction_ = COMPARISON_FUNCTION_TABLE[comparisonName];
  5111.           this.doSortBackwards_ = sortInfo.isNegated;
  5112.         }
  5113.         filterText = sortInfo.remainingText;
  5114.       }
  5115.  
  5116.       return filterText;
  5117.     },
  5118.  
  5119.     /**
  5120.      * Parse any "is:" directives, and update |filter| accordingly.
  5121.      *
  5122.      * Returns |filterText| with all "is:" directives removed, including
  5123.      * invalid ones.
  5124.      */
  5125.     parseRestrictDirectives_: function(filterText, filter) {
  5126.       while (true) {
  5127.         var filterInfo = this.parseNegatableDirective_(filterText, 'is');
  5128.         if (filterInfo == null)
  5129.           break;
  5130.         if (filterInfo.parameter == 'active') {
  5131.           if (!filterInfo.isNegated) {
  5132.             filter.isActive = true;
  5133.           } else {
  5134.             filter.isInactive = true;
  5135.           }
  5136.         }
  5137.         if (filterInfo.parameter == 'error') {
  5138.           if (!filterInfo.isNegated) {
  5139.             filter.isError = true;
  5140.           } else {
  5141.             filter.isNotError = true;
  5142.           }
  5143.         }
  5144.         filterText = filterInfo.remainingText;
  5145.       }
  5146.       return filterText;
  5147.     },
  5148.  
  5149.     /**
  5150.      * Parses all directives that take arbitrary strings as input,
  5151.      * and updates |filter| accordingly.  Directives of these types
  5152.      * are stored as lists.
  5153.      *
  5154.      * Returns |filterText| with all recognized directives removed.
  5155.      */
  5156.     parseStringDirectives_: function(filterText, filter) {
  5157.       var directives = ['type', 'id'];
  5158.       for (var i = 0; i < directives.length; ++i) {
  5159.         while (true) {
  5160.           var directive = directives[i];
  5161.           var filterInfo = this.parseDirective_(filterText, directive);
  5162.           if (filterInfo == null)
  5163.             break;
  5164.  
  5165.           // Split parameters around commas and remove empty elements.
  5166.           var parameters = filterInfo.parameter.split(',');
  5167.           parameters = parameters.filter(function(string) {
  5168.               return string.length > 0;
  5169.           });
  5170.  
  5171.           // If there's already a matching filter, take the intersection.
  5172.           // This behavior primarily exists for tests.  It is not correct
  5173.           // when one of the 'type' filters is a partial match.
  5174.           if (filter[directive]) {
  5175.             parameters = parameters.filter(function(string) {
  5176.                 return filter[directive].indexOf(string) != -1;
  5177.             });
  5178.           }
  5179.  
  5180.           filter[directive] = parameters;
  5181.           filterText = filterInfo.remainingText;
  5182.         }
  5183.       }
  5184.       return filterText;
  5185.     },
  5186.  
  5187.     /*
  5188.      * Converts |filterText| into an object representing the filter.
  5189.      */
  5190.     createFilter_: function(filterText) {
  5191.       var filter = {};
  5192.       filterText = filterText.toLowerCase();
  5193.       filterText = this.parseRestrictDirectives_(filterText, filter);
  5194.       filterText = this.parseStringDirectives_(filterText, filter);
  5195.       filter.text = filterText.trim();
  5196.       return filter;
  5197.     },
  5198.  
  5199.     setFilter_: function(filterText) {
  5200.       var lastComparisonFunction = this.comparisonFunction_;
  5201.       var lastDoSortBackwards = this.doSortBackwards_;
  5202.  
  5203.       filterText = this.parseSortDirectives_(filterText);
  5204.  
  5205.       if (lastComparisonFunction != this.comparisonFunction_ ||
  5206.           lastDoSortBackwards != this.doSortBackwards_) {
  5207.         this.sort_();
  5208.       }
  5209.  
  5210.       this.currentFilter_ = this.createFilter_(filterText);
  5211.  
  5212.       // Iterate through all of the rows and see if they match the filter.
  5213.       for (var id in this.sourceIdToRowMap_) {
  5214.         var entry = this.sourceIdToRowMap_[id];
  5215.         entry.setIsMatchedByFilter(entry.matchesFilter(this.currentFilter_));
  5216.       }
  5217.     },
  5218.  
  5219.     /**
  5220.      * Repositions |sourceRow|'s in the table using an insertion sort.
  5221.      * Significantly faster than sorting the entire table again, when only
  5222.      * one entry has changed.
  5223.      */
  5224.     insertionSort_: function(sourceRow) {
  5225.       // SourceRow that should be after |sourceRow|, if it needs
  5226.       // to be moved earlier in the list.
  5227.       var sourceRowAfter = sourceRow;
  5228.       while (true) {
  5229.         var prevSourceId = sourceRowAfter.getPreviousNodeSourceId();
  5230.         if (prevSourceId == null)
  5231.           break;
  5232.         var prevSourceRow = this.sourceIdToRowMap_[prevSourceId];
  5233.         if (this.comparisonFuncWithReversing_(
  5234.                 sourceRow.getSourceEntry(),
  5235.                 prevSourceRow.getSourceEntry()) >= 0) {
  5236.           break;
  5237.         }
  5238.         sourceRowAfter = prevSourceRow;
  5239.       }
  5240.       if (sourceRowAfter != sourceRow) {
  5241.         sourceRow.moveBefore(sourceRowAfter);
  5242.         return;
  5243.       }
  5244.  
  5245.       var sourceRowBefore = sourceRow;
  5246.       while (true) {
  5247.         var nextSourceId = sourceRowBefore.getNextNodeSourceId();
  5248.         if (nextSourceId == null)
  5249.           break;
  5250.         var nextSourceRow = this.sourceIdToRowMap_[nextSourceId];
  5251.         if (this.comparisonFuncWithReversing_(
  5252.                 sourceRow.getSourceEntry(),
  5253.                 nextSourceRow.getSourceEntry()) <= 0) {
  5254.           break;
  5255.         }
  5256.         sourceRowBefore = nextSourceRow;
  5257.       }
  5258.       if (sourceRowBefore != sourceRow)
  5259.         sourceRow.moveAfter(sourceRowBefore);
  5260.     },
  5261.  
  5262.     /**
  5263.      * Called whenever SourceEntries are updated with new log entries.  Updates
  5264.      * the corresponding table rows, sort order, and the details view as needed.
  5265.      */
  5266.     onSourceEntriesUpdated: function(sourceEntries) {
  5267.       var isUpdatedSourceSelected = false;
  5268.       var numNewSourceEntries = 0;
  5269.  
  5270.       for (var i = 0; i < sourceEntries.length; ++i) {
  5271.         var sourceEntry = sourceEntries[i];
  5272.  
  5273.         // Lookup the row.
  5274.         var sourceRow = this.sourceIdToRowMap_[sourceEntry.getSourceId()];
  5275.  
  5276.         if (!sourceRow) {
  5277.           sourceRow = new SourceRow(this, sourceEntry);
  5278.           this.sourceIdToRowMap_[sourceEntry.getSourceId()] = sourceRow;
  5279.           ++numNewSourceEntries;
  5280.         } else {
  5281.           sourceRow.onSourceUpdated();
  5282.         }
  5283.  
  5284.         if (sourceRow.isSelected())
  5285.           isUpdatedSourceSelected = true;
  5286.  
  5287.         // TODO(mmenke): Fix sorting when sorting by duration.
  5288.         //               Duration continuously increases for all entries that
  5289.         //               are still active.  This can result in incorrect
  5290.         //               sorting, until sort_ is called.
  5291.         this.insertionSort_(sourceRow);
  5292.       }
  5293.  
  5294.       if (isUpdatedSourceSelected)
  5295.         this.invalidateDetailsView_();
  5296.       if (numNewSourceEntries)
  5297.         this.incrementPrefilterCount(numNewSourceEntries);
  5298.     },
  5299.  
  5300.     /**
  5301.      * Returns the SourceRow with the specified ID, if there is one.
  5302.      * Otherwise, returns undefined.
  5303.      */
  5304.     getSourceRow: function(id) {
  5305.       return this.sourceIdToRowMap_[id];
  5306.     },
  5307.  
  5308.     /**
  5309.      * Called whenever all log events are deleted.
  5310.      */
  5311.     onAllSourceEntriesDeleted: function() {
  5312.       this.initializeSourceList_();
  5313.     },
  5314.  
  5315.     /**
  5316.      * Called when either a log file is loaded, after clearing the old entries,
  5317.      * but before getting any new ones.
  5318.      */
  5319.     onLoadLogStart: function() {
  5320.       // Needed to sort new sourceless entries correctly.
  5321.       this.maxReceivedSourceId_ = 0;
  5322.     },
  5323.  
  5324.     onLoadLogFinish: function(data) {
  5325.       return true;
  5326.     },
  5327.  
  5328.     incrementPrefilterCount: function(offset) {
  5329.       this.numPrefilter_ += offset;
  5330.       this.invalidateFilterCounter_();
  5331.     },
  5332.  
  5333.     incrementPostfilterCount: function(offset) {
  5334.       this.numPostfilter_ += offset;
  5335.       this.invalidateFilterCounter_();
  5336.     },
  5337.  
  5338.     onSelectionChanged: function() {
  5339.       this.invalidateDetailsView_();
  5340.     },
  5341.  
  5342.     clearSelection: function() {
  5343.       var prevSelection = this.currentSelectedRows_;
  5344.       this.currentSelectedRows_ = [];
  5345.  
  5346.       // Unselect everything that is currently selected.
  5347.       for (var i = 0; i < prevSelection.length; ++i) {
  5348.         prevSelection[i].setSelected(false);
  5349.       }
  5350.  
  5351.       this.onSelectionChanged();
  5352.     },
  5353.  
  5354.     selectAll_: function(event) {
  5355.       for (var id in this.sourceIdToRowMap_) {
  5356.         var sourceRow = this.sourceIdToRowMap_[id];
  5357.         if (sourceRow.isMatchedByFilter()) {
  5358.           sourceRow.setSelected(true);
  5359.         }
  5360.       }
  5361.       event.preventDefault();
  5362.     },
  5363.  
  5364.     unselectAll_: function() {
  5365.       var entries = this.currentSelectedRows_.slice(0);
  5366.       for (var i = 0; i < entries.length; ++i) {
  5367.         entries[i].setSelected(false);
  5368.       }
  5369.     },
  5370.  
  5371.     /**
  5372.      * If |params| includes a query, replaces the current filter and unselects.
  5373.      * all items.  If it includes a selection, tries to select the relevant
  5374.      * item.
  5375.      */
  5376.     setParameters: function(params) {
  5377.       if (params.q) {
  5378.         this.unselectAll_();
  5379.         this.setFilterText_(params.q);
  5380.       }
  5381.  
  5382.       if (params.s) {
  5383.         var sourceRow = this.sourceIdToRowMap_[params.s];
  5384.         if (sourceRow) {
  5385.           sourceRow.setSelected(true);
  5386.           this.scrollToSourceId(params.s);
  5387.         }
  5388.       }
  5389.     },
  5390.  
  5391.     /**
  5392.      * Scrolls to the source indicated by |sourceId|, if displayed.
  5393.      */
  5394.     scrollToSourceId: function(sourceId) {
  5395.       this.detailsView_.scrollToSourceId(sourceId);
  5396.     },
  5397.  
  5398.     /**
  5399.      * If already using the specified sort method, flips direction.  Otherwise,
  5400.      * removes pre-existing sort parameter before adding the new one.
  5401.      */
  5402.     toggleSortMethod_: function(sortMethod) {
  5403.       // Remove old sort directives, if any.
  5404.       var filterText = this.parseSortDirectives_(this.getFilterText_());
  5405.  
  5406.       // If already using specified sortMethod, sort backwards.
  5407.       if (!this.doSortBackwards_ &&
  5408.           COMPARISON_FUNCTION_TABLE[sortMethod] == this.comparisonFunction_)
  5409.         sortMethod = '-' + sortMethod;
  5410.  
  5411.       filterText = 'sort:' + sortMethod + ' ' + filterText;
  5412.       this.setFilterText_(filterText.trim());
  5413.     },
  5414.  
  5415.     sortById_: function(event) {
  5416.       this.toggleSortMethod_('id');
  5417.     },
  5418.  
  5419.     sortBySourceType_: function(event) {
  5420.       this.toggleSortMethod_('source');
  5421.     },
  5422.  
  5423.     sortByDescription_: function(event) {
  5424.       this.toggleSortMethod_('desc');
  5425.     },
  5426.  
  5427.     /**
  5428.      * Modifies the map of selected rows to include/exclude the one with
  5429.      * |sourceId|, if present.  Does not modify checkboxes or the LogView.
  5430.      * Should only be called by a SourceRow in response to its selection
  5431.      * state changing.
  5432.      */
  5433.     modifySelectionArray: function(sourceId, addToSelection) {
  5434.       var sourceRow = this.sourceIdToRowMap_[sourceId];
  5435.       if (!sourceRow)
  5436.         return;
  5437.       // Find the index for |sourceEntry| in the current selection list.
  5438.       var index = -1;
  5439.       for (var i = 0; i < this.currentSelectedRows_.length; ++i) {
  5440.         if (this.currentSelectedRows_[i] == sourceRow) {
  5441.           index = i;
  5442.           break;
  5443.         }
  5444.       }
  5445.  
  5446.       if (index != -1 && !addToSelection) {
  5447.         // Remove from the selection.
  5448.         this.currentSelectedRows_.splice(index, 1);
  5449.       }
  5450.  
  5451.       if (index == -1 && addToSelection) {
  5452.         this.currentSelectedRows_.push(sourceRow);
  5453.       }
  5454.     },
  5455.  
  5456.     getSelectedSourceEntries_: function() {
  5457.       var sourceEntries = [];
  5458.       for (var i = 0; i < this.currentSelectedRows_.length; ++i) {
  5459.         sourceEntries.push(this.currentSelectedRows_[i].getSourceEntry());
  5460.       }
  5461.       return sourceEntries;
  5462.     },
  5463.  
  5464.     invalidateDetailsView_: function() {
  5465.       this.detailsView_.setData(this.getSelectedSourceEntries_());
  5466.     },
  5467.  
  5468.     invalidateFilterCounter_: function() {
  5469.       if (!this.outstandingRepaintFilterCounter_) {
  5470.         this.outstandingRepaintFilterCounter_ = true;
  5471.         window.setTimeout(this.repaintFilterCounter_.bind(this),
  5472.                           REPAINT_FILTER_COUNTER_TIMEOUT_MS);
  5473.       }
  5474.     },
  5475.  
  5476.     repaintFilterCounter_: function() {
  5477.       this.outstandingRepaintFilterCounter_ = false;
  5478.       this.filterCount_.innerHTML = '';
  5479.       addTextNode(this.filterCount_,
  5480.                   this.numPostfilter_ + ' of ' + this.numPrefilter_);
  5481.     }
  5482.   };  // end of prototype.
  5483.  
  5484.   // ------------------------------------------------------------------------
  5485.   // Helper code for comparisons
  5486.   // ------------------------------------------------------------------------
  5487.  
  5488.   var COMPARISON_FUNCTION_TABLE = {
  5489.     // sort: and sort:- are allowed
  5490.     '': compareSourceId,
  5491.     'active': compareActive,
  5492.     'desc': compareDescription,
  5493.     'description': compareDescription,
  5494.     'duration': compareDuration,
  5495.     'id': compareSourceId,
  5496.     'source': compareSourceType,
  5497.     'type': compareSourceType
  5498.   };
  5499.  
  5500.   /**
  5501.    * Sorts active entries first.  If both entries are inactive, puts the one
  5502.    * that was active most recently first.  If both are active, uses source ID,
  5503.    * which puts longer lived events at the top, and behaves better than using
  5504.    * duration or time of first event.
  5505.    */
  5506.   function compareActive(source1, source2) {
  5507.     if (!source1.isInactive() && source2.isInactive())
  5508.       return -1;
  5509.     if (source1.isInactive() && !source2.isInactive())
  5510.       return 1;
  5511.     if (source1.isInactive()) {
  5512.       var deltaEndTime = source1.getEndTime() - source2.getEndTime();
  5513.       if (deltaEndTime != 0) {
  5514.         // The one that ended most recently (Highest end time) should be sorted
  5515.         // first.
  5516.         return -deltaEndTime;
  5517.       }
  5518.       // If both ended at the same time, then odds are they were related events,
  5519.       // started one after another, so sort in the opposite order of their
  5520.       // source IDs to get a more intuitive ordering.
  5521.       return -compareSourceId(source1, source2);
  5522.     }
  5523.     return compareSourceId(source1, source2);
  5524.   }
  5525.  
  5526.   function compareDescription(source1, source2) {
  5527.     var source1Text = source1.getDescription().toLowerCase();
  5528.     var source2Text = source2.getDescription().toLowerCase();
  5529.     var compareResult = source1Text.localeCompare(source2Text);
  5530.     if (compareResult != 0)
  5531.       return compareResult;
  5532.     return compareSourceId(source1, source2);
  5533.   }
  5534.  
  5535.   function compareDuration(source1, source2) {
  5536.     var durationDifference = source2.getDuration() - source1.getDuration();
  5537.     if (durationDifference)
  5538.       return durationDifference;
  5539.     return compareSourceId(source1, source2);
  5540.   }
  5541.  
  5542.   /**
  5543.    * For the purposes of sorting by source IDs, entries without a source
  5544.    * appear right after the SourceEntry with the highest source ID received
  5545.    * before the sourceless entry. Any ambiguities are resolved by ordering
  5546.    * the entries without a source by the order in which they were received.
  5547.    */
  5548.   function compareSourceId(source1, source2) {
  5549.     var sourceId1 = source1.getSourceId();
  5550.     if (sourceId1 < 0)
  5551.       sourceId1 = source1.getMaxPreviousEntrySourceId();
  5552.     var sourceId2 = source2.getSourceId();
  5553.     if (sourceId2 < 0)
  5554.       sourceId2 = source2.getMaxPreviousEntrySourceId();
  5555.  
  5556.     if (sourceId1 != sourceId2)
  5557.       return sourceId1 - sourceId2;
  5558.  
  5559.     // One or both have a negative ID. In either case, the source with the
  5560.     // highest ID should be sorted first.
  5561.     return source2.getSourceId() - source1.getSourceId();
  5562.   }
  5563.  
  5564.   function compareSourceType(source1, source2) {
  5565.     var source1Text = source1.getSourceTypeString();
  5566.     var source2Text = source2.getSourceTypeString();
  5567.     var compareResult = source1Text.localeCompare(source2Text);
  5568.     if (compareResult != 0)
  5569.       return compareResult;
  5570.     return compareSourceId(source1, source2);
  5571.   }
  5572.  
  5573.   return EventsView;
  5574. })();
  5575.  
  5576. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  5577. // Use of this source code is governed by a BSD-style license that can be
  5578. // found in the LICENSE file.
  5579.  
  5580. var DetailsView = (function() {
  5581.   'use strict';
  5582.  
  5583.   // We inherit from DivView.
  5584.   var superClass = DivView;
  5585.  
  5586.   /**
  5587.    * The DetailsView displays the "log" view. This class keeps track of the
  5588.    * selected SourceEntries, and repaints when they change.
  5589.    *
  5590.    * @constructor
  5591.    */
  5592.   function DetailsView(boxId) {
  5593.     superClass.call(this, boxId);
  5594.     this.sourceEntries_ = [];
  5595.     // Map of source IDs to their corresponding DIVs.
  5596.     this.sourceIdToDivMap_ = {};
  5597.     // True when there's an asychronous repaint outstanding.
  5598.     this.outstandingRepaint_ = false;
  5599.     // ID of source entry we should jump to after the oustanding repaint.
  5600.     // 0 if none, or there's no such repaint.
  5601.     this.outstandingScrollToId_ = 0;
  5602.   }
  5603.  
  5604.   // The delay between updates to repaint.
  5605.   var REPAINT_TIMEOUT_MS = 50;
  5606.  
  5607.   DetailsView.prototype = {
  5608.     // Inherit the superclass's methods.
  5609.     __proto__: superClass.prototype,
  5610.  
  5611.     setData: function(sourceEntries) {
  5612.       // Make a copy of the array (in case the caller mutates it), and sort it
  5613.       // by the source ID.
  5614.       this.sourceEntries_ = createSortedCopy_(sourceEntries);
  5615.  
  5616.       // Repaint the view.
  5617.       if (this.isVisible() && !this.outstandingRepaint_) {
  5618.         this.outstandingRepaint_ = true;
  5619.         window.setTimeout(this.repaint.bind(this),
  5620.                           REPAINT_TIMEOUT_MS);
  5621.       }
  5622.     },
  5623.  
  5624.     repaint: function() {
  5625.       this.outstandingRepaint_ = false;
  5626.       this.sourceIdToDivMap_ = {};
  5627.       this.getNode().innerHTML = '';
  5628.  
  5629.       var node = this.getNode();
  5630.  
  5631.       for (var i = 0; i < this.sourceEntries_.length; ++i) {
  5632.         if (i != 0)
  5633.           addNode(node, 'hr');
  5634.  
  5635.         var sourceEntry = this.sourceEntries_[i];
  5636.         var div = addNode(node, 'div');
  5637.         div.className = 'log-source-entry';
  5638.  
  5639.         var p = addNode(div, 'p');
  5640.         addNodeWithText(p, 'h4',
  5641.                         sourceEntry.getSourceId() + ': ' +
  5642.                             sourceEntry.getSourceTypeString());
  5643.  
  5644.         if (sourceEntry.getDescription())
  5645.           addNodeWithText(p, 'h4', sourceEntry.getDescription());
  5646.  
  5647.         var logEntries = sourceEntry.getLogEntries();
  5648.         var startDate = timeutil.convertTimeTicksToDate(logEntries[0].time);
  5649.         var startTimeDiv = addNodeWithText(p, 'div', 'Start Time: ');
  5650.         timeutil.addNodeWithDate(startTimeDiv, startDate);
  5651.  
  5652.         sourceEntry.printAsText(div);
  5653.  
  5654.         this.sourceIdToDivMap_[sourceEntry.getSourceId()] = div;
  5655.       }
  5656.  
  5657.       if (this.outstandingScrollToId_) {
  5658.         this.scrollToSourceId(this.outstandingScrollToId_);
  5659.         this.outstandingScrollToId_ = 0;
  5660.       }
  5661.     },
  5662.  
  5663.     show: function(isVisible) {
  5664.       superClass.prototype.show.call(this, isVisible);
  5665.       if (isVisible) {
  5666.         this.repaint();
  5667.       } else {
  5668.         this.getNode().innerHTML = '';
  5669.       }
  5670.     },
  5671.  
  5672.     /**
  5673.      * Scrolls to the source indicated by |sourceId|, if displayed.  If a
  5674.      * repaint is outstanding, waits for it to complete before scrolling.
  5675.      */
  5676.     scrollToSourceId: function(sourceId) {
  5677.       if (this.outstandingRepaint_) {
  5678.         this.outstandingScrollToId_ = sourceId;
  5679.         return;
  5680.       }
  5681.       var div = this.sourceIdToDivMap_[sourceId];
  5682.       if (div)
  5683.         div.scrollIntoView();
  5684.     }
  5685.   };
  5686.  
  5687.   function createSortedCopy_(origArray) {
  5688.     var sortedArray = origArray.slice(0);
  5689.     sortedArray.sort(function(a, b) {
  5690.       return a.getSourceId() - b.getSourceId();
  5691.     });
  5692.     return sortedArray;
  5693.   }
  5694.  
  5695.   return DetailsView;
  5696. })();
  5697.  
  5698. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  5699. // Use of this source code is governed by a BSD-style license that can be
  5700. // found in the LICENSE file.
  5701.  
  5702. var SourceEntry = (function() {
  5703.   'use strict';
  5704.  
  5705.   /**
  5706.    * A SourceEntry gathers all log entries with the same source.
  5707.    *
  5708.    * @constructor
  5709.    */
  5710.   function SourceEntry(logEntry, maxPreviousSourceId) {
  5711.     this.maxPreviousSourceId_ = maxPreviousSourceId;
  5712.     this.entries_ = [];
  5713.     this.description_ = '';
  5714.  
  5715.     // Set to true on most net errors.
  5716.     this.isError_ = false;
  5717.  
  5718.     // If the first entry is a BEGIN_PHASE, set to false.
  5719.     // Set to true when an END_PHASE matching the first entry is encountered.
  5720.     this.isInactive_ = true;
  5721.  
  5722.     if (logEntry.phase == EventPhase.PHASE_BEGIN)
  5723.       this.isInactive_ = false;
  5724.  
  5725.     this.update(logEntry);
  5726.   }
  5727.  
  5728.   SourceEntry.prototype = {
  5729.     update: function(logEntry) {
  5730.       // Only the last event should have the same type first event,
  5731.       if (!this.isInactive_ &&
  5732.           logEntry.phase == EventPhase.PHASE_END &&
  5733.           logEntry.type == this.entries_[0].type) {
  5734.         this.isInactive_ = true;
  5735.       }
  5736.  
  5737.       // If we have a net error code, update |this.isError_| if apporpriate.
  5738.       if (logEntry.params) {
  5739.         var netErrorCode = logEntry.params.net_error;
  5740.         // Skip both cases where netErrorCode is undefined, and cases where it
  5741.         // is 0, indicating no actual error occurred.
  5742.         if (netErrorCode) {
  5743.           // Ignore error code caused by not finding an entry in the cache.
  5744.           if (logEntry.type != EventType.HTTP_CACHE_OPEN_ENTRY ||
  5745.               netErrorCode != NetError.FAILED) {
  5746.             this.isError_ = true;
  5747.           }
  5748.         }
  5749.       }
  5750.  
  5751.       var prevStartEntry = this.getStartEntry_();
  5752.       this.entries_.push(logEntry);
  5753.       var curStartEntry = this.getStartEntry_();
  5754.  
  5755.       // If we just got the first entry for this source.
  5756.       if (prevStartEntry != curStartEntry)
  5757.         this.updateDescription_();
  5758.     },
  5759.  
  5760.     updateDescription_: function() {
  5761.       var e = this.getStartEntry_();
  5762.       this.description_ = '';
  5763.       if (!e)
  5764.         return;
  5765.  
  5766.       if (e.source.type == EventSourceType.NONE) {
  5767.         // NONE is what we use for global events that aren't actually grouped
  5768.         // by a "source ID", so we will just stringize the event's type.
  5769.         this.description_ = EventTypeNames[e.type];
  5770.         return;
  5771.       }
  5772.  
  5773.       if (e.params == undefined) {
  5774.         return;
  5775.       }
  5776.  
  5777.       switch (e.source.type) {
  5778.         case EventSourceType.URL_REQUEST:
  5779.         case EventSourceType.SOCKET_STREAM:
  5780.         case EventSourceType.HTTP_STREAM_JOB:
  5781.           this.description_ = e.params.url;
  5782.           break;
  5783.         case EventSourceType.CONNECT_JOB:
  5784.           this.description_ = e.params.group_name;
  5785.           break;
  5786.         case EventSourceType.HOST_RESOLVER_IMPL_REQUEST:
  5787.         case EventSourceType.HOST_RESOLVER_IMPL_JOB:
  5788.         case EventSourceType.HOST_RESOLVER_IMPL_PROC_TASK:
  5789.           this.description_ = e.params.host;
  5790.           break;
  5791.         case EventSourceType.DISK_CACHE_ENTRY:
  5792.         case EventSourceType.MEMORY_CACHE_ENTRY:
  5793.           this.description_ = e.params.key;
  5794.           break;
  5795.         case EventSourceType.SPDY_SESSION:
  5796.           if (e.params.host)
  5797.             this.description_ = e.params.host + ' (' + e.params.proxy + ')';
  5798.           break;
  5799.         case EventSourceType.HTTP_PIPELINED_CONNECTION:
  5800.           if (e.params.host_and_port)
  5801.             this.description_ = e.params.host_and_port;
  5802.           break;
  5803.         case EventSourceType.SOCKET:
  5804.           // Use description of parent source, if any.
  5805.           if (e.params.source_dependency != undefined) {
  5806.             var parentId = e.params.source_dependency.id;
  5807.             this.description_ =
  5808.                 SourceTracker.getInstance().getDescription(parentId);
  5809.           }
  5810.           break;
  5811.         case EventSourceType.UDP_SOCKET:
  5812.           if (e.params.address != undefined) {
  5813.             this.description_ = e.params.address;
  5814.             // If the parent of |this| is a HOST_RESOLVER_IMPL_JOB, use
  5815.             // '<DNS Server IP> [<host we're resolving>]'.
  5816.             if (this.entries_[0].type == EventType.SOCKET_ALIVE &&
  5817.                 this.entries_[0].params &&
  5818.                 this.entries_[0].params.source_dependency != undefined) {
  5819.               var parentId = this.entries_[0].params.source_dependency.id;
  5820.               var parent = SourceTracker.getInstance().getSourceEntry(parentId);
  5821.               if (parent &&
  5822.                   parent.getSourceType() ==
  5823.                       EventSourceType.HOST_RESOLVER_IMPL_JOB &&
  5824.                   parent.getDescription().length > 0) {
  5825.                 this.description_ += ' [' + parent.getDescription() + ']';
  5826.               }
  5827.             }
  5828.           }
  5829.           break;
  5830.         case EventSourceType.ASYNC_HOST_RESOLVER_REQUEST:
  5831.         case EventSourceType.DNS_TRANSACTION:
  5832.           this.description_ = e.params.hostname;
  5833.           break;
  5834.         case EventSourceType.DOWNLOAD:
  5835.           switch (e.type) {
  5836.             case EventType.DOWNLOAD_FILE_RENAMED:
  5837.               this.description_ = e.params.new_filename;
  5838.               break;
  5839.             case EventType.DOWNLOAD_FILE_OPENED:
  5840.               this.description_ = e.params.file_name;
  5841.               break;
  5842.             case EventType.DOWNLOAD_ITEM_ACTIVE:
  5843.               this.description_ = e.params.file_name;
  5844.               break;
  5845.           }
  5846.           break;
  5847.         case EventSourceType.FILESTREAM:
  5848.           this.description_ = e.params.file_name;
  5849.           break;
  5850.         case EventSourceType.IPV6_PROBE_JOB:
  5851.           if (e.type == EventType.IPV6_PROBE_RUNNING &&
  5852.               e.phase == EventPhase.PHASE_END) {
  5853.             this.description_ = e.params.ipv6_supported ? 'IPv6 Supported' :
  5854.                                                           'IPv6 Not Supported';
  5855.           }
  5856.           break;
  5857.       }
  5858.  
  5859.       if (this.description_ == undefined)
  5860.         this.description_ = '';
  5861.     },
  5862.  
  5863.     /**
  5864.      * Returns a description for this source log stream, which will be displayed
  5865.      * in the list view. Most often this is a URL that identifies the request,
  5866.      * or a hostname for a connect job, etc...
  5867.      */
  5868.     getDescription: function() {
  5869.       return this.description_;
  5870.     },
  5871.  
  5872.     /**
  5873.      * Returns the starting entry for this source. Conceptually this is the
  5874.      * first entry that was logged to this source. However, we skip over the
  5875.      * TYPE_REQUEST_ALIVE entries which wrap TYPE_URL_REQUEST_START_JOB /
  5876.      * TYPE_SOCKET_STREAM_CONNECT.
  5877.      */
  5878.     getStartEntry_: function() {
  5879.       if (this.entries_.length < 1)
  5880.         return undefined;
  5881.       if (this.entries_[0].source.type == EventSourceType.FILESTREAM) {
  5882.         var e = this.findLogEntryByType_(EventType.FILE_STREAM_OPEN);
  5883.         if (e != undefined)
  5884.           return e;
  5885.       }
  5886.       if (this.entries_[0].source.type == EventSourceType.DOWNLOAD) {
  5887.         // If any rename occurred, use the last name
  5888.         e = this.findLastLogEntryStartByType_(
  5889.             EventType.DOWNLOAD_FILE_RENAMED);
  5890.         if (e != undefined)
  5891.           return e;
  5892.         // Otherwise, if the file was opened, use that name
  5893.         e = this.findLogEntryByType_(EventType.DOWNLOAD_FILE_OPENED);
  5894.         if (e != undefined)
  5895.           return e;
  5896.         // History items are never opened, so use the activation info
  5897.         e = this.findLogEntryByType_(EventType.DOWNLOAD_ITEM_ACTIVE);
  5898.         if (e != undefined)
  5899.           return e;
  5900.       }
  5901.       if (this.entries_.length >= 2) {
  5902.         if (this.entries_[0].type == EventType.SOCKET_POOL_CONNECT_JOB ||
  5903.             this.entries_[1].type == EventType.UDP_CONNECT) {
  5904.           return this.entries_[1];
  5905.         }
  5906.         if (this.entries_[0].type == EventType.REQUEST_ALIVE &&
  5907.             this.entries_[0].params == undefined) {
  5908.           var start_index = 1;
  5909.           // Skip over URL_REQUEST_BLOCKED_ON_DELEGATE events for URL_REQUESTs.
  5910.           while (start_index + 1 < this.entries_.length &&
  5911.                  this.entries_[start_index].type ==
  5912.                      EventType.URL_REQUEST_BLOCKED_ON_DELEGATE) {
  5913.             ++start_index;
  5914.           }
  5915.           return this.entries_[start_index];
  5916.         }
  5917.         if (this.entries_[1].type == EventType.IPV6_PROBE_RUNNING)
  5918.           return this.entries_[1];
  5919.       }
  5920.       return this.entries_[0];
  5921.     },
  5922.  
  5923.     /**
  5924.      * Returns the first entry with the specified type, or undefined if not
  5925.      * found.
  5926.      */
  5927.     findLogEntryByType_: function(type) {
  5928.       for (var i = 0; i < this.entries_.length; ++i) {
  5929.         if (this.entries_[i].type == type) {
  5930.           return this.entries_[i];
  5931.         }
  5932.       }
  5933.       return undefined;
  5934.     },
  5935.  
  5936.     /**
  5937.      * Returns the beginning of the last entry with the specified type, or
  5938.      * undefined if not found.
  5939.      */
  5940.     findLastLogEntryStartByType_: function(type) {
  5941.       for (var i = this.entries_.length - 1; i >= 0; --i) {
  5942.         if (this.entries_[i].type == type) {
  5943.           if (this.entries_[i].phase != EventPhase.PHASE_END)
  5944.             return this.entries_[i];
  5945.         }
  5946.       }
  5947.       return undefined;
  5948.     },
  5949.  
  5950.     getLogEntries: function() {
  5951.       return this.entries_;
  5952.     },
  5953.  
  5954.     getSourceTypeString: function() {
  5955.       return EventSourceTypeNames[this.entries_[0].source.type];
  5956.     },
  5957.  
  5958.     getSourceType: function() {
  5959.       return this.entries_[0].source.type;
  5960.     },
  5961.  
  5962.     getSourceId: function() {
  5963.       return this.entries_[0].source.id;
  5964.     },
  5965.  
  5966.     /**
  5967.      * Returns the largest source ID seen before this object was received.
  5968.      * Used only for sorting SourceEntries without a source by source ID.
  5969.      */
  5970.     getMaxPreviousEntrySourceId: function() {
  5971.       return this.maxPreviousSourceId_;
  5972.     },
  5973.  
  5974.     isInactive: function() {
  5975.       return this.isInactive_;
  5976.     },
  5977.  
  5978.     isError: function() {
  5979.       return this.isError_;
  5980.     },
  5981.  
  5982.     /**
  5983.      * Returns time of last event if inactive.  Returns current time otherwise.
  5984.      */
  5985.     getEndTime: function() {
  5986.       if (!this.isInactive_) {
  5987.         return timeutil.getCurrentTime();
  5988.       } else {
  5989.         var endTicks = this.entries_[this.entries_.length - 1].time;
  5990.         return timeutil.convertTimeTicksToTime(endTicks);
  5991.       }
  5992.     },
  5993.  
  5994.     /**
  5995.      * Returns the time between the first and last events with a matching
  5996.      * source ID.  If source is still active, uses the current time for the
  5997.      * last event.
  5998.      */
  5999.     getDuration: function() {
  6000.       var startTicks = this.entries_[0].time;
  6001.       var startTime = timeutil.convertTimeTicksToTime(startTicks);
  6002.       var endTime = this.getEndTime();
  6003.       return endTime - startTime;
  6004.     },
  6005.  
  6006.     /**
  6007.      * Prints descriptive text about |entries_| to a new node added to the end
  6008.      * of |parent|.
  6009.      */
  6010.     printAsText: function(parent) {
  6011.       // The date will be undefined if not viewing a loaded log file.
  6012.       printLogEntriesAsText(this.entries_, parent,
  6013.                             SourceTracker.getInstance().getPrivacyStripping(),
  6014.                             Constants.clientInfo.numericDate);
  6015.     }
  6016.   };
  6017.  
  6018.   return SourceEntry;
  6019. })();
  6020.  
  6021. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  6022. // Use of this source code is governed by a BSD-style license that can be
  6023. // found in the LICENSE file.
  6024.  
  6025. /**
  6026.  * This view consists of two nested divs.  The outer one has a horizontal
  6027.  * scrollbar and the inner one has a height of 1 pixel and a width set to
  6028.  * allow an appropriate scroll range.  The view reports scroll events to
  6029.  * a callback specified on construction.
  6030.  *
  6031.  * All this funkiness is necessary because there is no HTML scroll control.
  6032.  * TODO(mmenke):  Consider implementing our own scrollbar directly.
  6033.  */
  6034. var HorizontalScrollbarView = (function() {
  6035.   'use strict';
  6036.  
  6037.   // We inherit from DivView.
  6038.   var superClass = DivView;
  6039.  
  6040.   /**
  6041.    * @constructor
  6042.    */
  6043.   function HorizontalScrollbarView(divId, innerDivId, callback) {
  6044.     superClass.call(this, divId);
  6045.     this.callback_ = callback;
  6046.     this.innerDiv_ = $(innerDivId);
  6047.     $(divId).onscroll = this.onScroll_.bind(this);
  6048.  
  6049.     // The current range and position of the scrollbar.  Because DOM updates
  6050.     // are asynchronous, the current state cannot be read directly from the DOM
  6051.     // after updating the range.
  6052.     this.range_ = 0;
  6053.     this.position_ = 0;
  6054.  
  6055.     // The DOM updates asynchronously, so sometimes we need a timer to update
  6056.     // the current scroll position after resizing the scrollbar.
  6057.     this.updatePositionTimerId_ = null;
  6058.   }
  6059.  
  6060.   HorizontalScrollbarView.prototype = {
  6061.     // Inherit the superclass's methods.
  6062.     __proto__: superClass.prototype,
  6063.  
  6064.     setGeometry: function(left, top, width, height) {
  6065.       superClass.prototype.setGeometry.call(this, left, top, width, height);
  6066.       this.setRange(this.range_);
  6067.     },
  6068.  
  6069.     show: function(isVisible) {
  6070.       superClass.prototype.show.call(this, isVisible);
  6071.     },
  6072.  
  6073.     /**
  6074.      * Sets the range of the scrollbar.  The scrollbar can have a value
  6075.      * anywhere from 0 to |range|, inclusive.  The width of the drag area
  6076.      * on the scrollbar will generally be based on the width of the scrollbar
  6077.      * relative to the size of |range|, so if the scrollbar is about the size
  6078.      * of the thing we're scrolling, we get fairly nice behavior.
  6079.      *
  6080.      * If |range| is less than the original position, |position_| is set to
  6081.      * |range|.  Otherwise, it is not modified.
  6082.      */
  6083.     setRange: function(range) {
  6084.       this.range_ = range;
  6085.       setNodeWidth(this.innerDiv_, this.getWidth() + range);
  6086.       if (range < this.position_)
  6087.         this.position_ = range;
  6088.       this.setPosition(this.position_);
  6089.     },
  6090.  
  6091.     /**
  6092.      * Sets the position of the scrollbar.  |position| must be between 0 and
  6093.      * |range_|, inclusive.
  6094.      */
  6095.     setPosition: function(position) {
  6096.       this.position_ = position;
  6097.       this.updatePosition_();
  6098.     },
  6099.  
  6100.     /**
  6101.      * Updates the visible position of the scrollbar to be |position_|.
  6102.      * On failure, calls itself again after a timeout.  This is needed because
  6103.      * setRange does not synchronously update the DOM.
  6104.      */
  6105.     updatePosition_: function() {
  6106.       // Clear the timer if we have one, so we don't have two timers running at
  6107.       // once.  This is safe even if we were just called from the timer, in
  6108.       // which case clearTimeout will silently fail.
  6109.       if (this.updatePositionTimerId_ !== null) {
  6110.         window.clearTimeout(this.updatePositionTimerId_);
  6111.         this.updatePositionTimerId_ = null;
  6112.       }
  6113.  
  6114.       this.getNode().scrollLeft = this.position_;
  6115.       if (this.getNode().scrollLeft != this.position_) {
  6116.         this.updatePositionTimerId_ =
  6117.             window.setTimeout(this.updatePosition_.bind(this));
  6118.       }
  6119.     },
  6120.  
  6121.     getRange: function() {
  6122.       return this.range_;
  6123.     },
  6124.  
  6125.     getPosition: function() {
  6126.       return this.position_;
  6127.     },
  6128.  
  6129.     onScroll_: function() {
  6130.       // If we're waiting to update the range, ignore messages from the
  6131.       // scrollbar.
  6132.       if (this.updatePositionTimerId_ !== null)
  6133.         return;
  6134.       var newPosition = this.getNode().scrollLeft;
  6135.       if (newPosition == this.position_)
  6136.         return;
  6137.       this.position_ = newPosition;
  6138.       this.callback_();
  6139.     }
  6140.   };
  6141.  
  6142.   return HorizontalScrollbarView;
  6143. })();
  6144.  
  6145. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  6146. // Use of this source code is governed by a BSD-style license that can be
  6147. // found in the LICENSE file.
  6148.  
  6149. var TopMidBottomView = (function() {
  6150.   'use strict';
  6151.  
  6152.   // We inherit from View.
  6153.   var superClass = View;
  6154.  
  6155.   /**
  6156.    * This view stacks three boxes -- one at the top, one at the bottom, and
  6157.    * one that fills the remaining space between those two.  Either the top
  6158.    * or the bottom bar may be null.
  6159.    *
  6160.    *  +----------------------+
  6161.    *  |      topbar          |
  6162.    *  +----------------------+
  6163.    *  |                      |
  6164.    *  |                      |
  6165.    *  |                      |
  6166.    *  |                      |
  6167.    *  |      middlebox       |
  6168.    *  |                      |
  6169.    *  |                      |
  6170.    *  |                      |
  6171.    *  |                      |
  6172.    *  |                      |
  6173.    *  +----------------------+
  6174.    *  |     bottombar        |
  6175.    *  +----------------------+
  6176.    *
  6177.    *  @constructor
  6178.    */
  6179.   function TopMidBottomView(topView, midView, bottomView) {
  6180.     superClass.call(this);
  6181.  
  6182.     this.topView_ = topView;
  6183.     this.midView_ = midView;
  6184.     this.bottomView_ = bottomView;
  6185.   }
  6186.  
  6187.   TopMidBottomView.prototype = {
  6188.     // Inherit the superclass's methods.
  6189.     __proto__: superClass.prototype,
  6190.  
  6191.     setGeometry: function(left, top, width, height) {
  6192.       superClass.prototype.setGeometry.call(this, left, top, width, height);
  6193.  
  6194.       // Calculate the vertical split points.
  6195.       var topbarHeight = 0;
  6196.       if (this.topView_)
  6197.         topbarHeight = this.topView_.getHeight();
  6198.       var bottombarHeight = 0;
  6199.       if (this.bottomView_)
  6200.         bottombarHeight = this.bottomView_.getHeight();
  6201.       var middleboxHeight = height - (topbarHeight + bottombarHeight);
  6202.       if (middleboxHeight < 0)
  6203.         middleboxHeight = 0;
  6204.  
  6205.       // Position the boxes using calculated split points.
  6206.       if (this.topView_)
  6207.         this.topView_.setGeometry(left, top, width, topbarHeight);
  6208.       this.midView_.setGeometry(left, top + topbarHeight, width,
  6209.                                 middleboxHeight);
  6210.       if (this.bottomView_) {
  6211.         this.bottomView_.setGeometry(left, top + topbarHeight + middleboxHeight,
  6212.                                      width, bottombarHeight);
  6213.       }
  6214.     },
  6215.  
  6216.     show: function(isVisible) {
  6217.       superClass.prototype.show.call(this, isVisible);
  6218.       if (this.topView_)
  6219.         this.topView_.show(isVisible);
  6220.       this.midView_.show(isVisible);
  6221.       if (this.bottomView_)
  6222.         this.bottomView_.show(isVisible);
  6223.     }
  6224.   };
  6225.  
  6226.   return TopMidBottomView;
  6227. })();
  6228.  
  6229. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  6230. // Use of this source code is governed by a BSD-style license that can be
  6231. // found in the LICENSE file.
  6232.  
  6233. /**
  6234.  * Different data types that each require their own labelled axis.
  6235.  */
  6236. var TimelineDataType = {
  6237.   SOURCE_COUNT: 0,
  6238.   BYTES_PER_SECOND: 1
  6239. };
  6240.  
  6241. /**
  6242.  * A TimelineDataSeries collects an ordered series of (time, value) pairs,
  6243.  * and converts them to graph points.  It also keeps track of its color and
  6244.  * current visibility state.  DataSeries are solely responsible for tracking
  6245.  * data, and do not send notifications on state changes.
  6246.  *
  6247.  * Abstract class, doesn't implement onReceivedLogEntry.
  6248.  */
  6249. var TimelineDataSeries = (function() {
  6250.   'use strict';
  6251.  
  6252.   /**
  6253.    * @constructor
  6254.    */
  6255.   function TimelineDataSeries(dataType) {
  6256.     // List of DataPoints in chronological order.
  6257.     this.dataPoints_ = [];
  6258.  
  6259.     // Data type of the DataSeries.  This is used to scale all values with
  6260.     // the same units in the same way.
  6261.     this.dataType_ = dataType;
  6262.     // Default color.  Should always be overridden prior to display.
  6263.     this.color_ = 'red';
  6264.     // Whether or not the data series should be drawn.
  6265.     this.isVisible_ = false;
  6266.  
  6267.     this.cacheStartTime_ = null;
  6268.     this.cacheStepSize_ = 0;
  6269.     this.cacheValues_ = [];
  6270.   }
  6271.  
  6272.   TimelineDataSeries.prototype = {
  6273.     /**
  6274.      * Adds a DataPoint to |this| with the specified time and value.
  6275.      * DataPoints are assumed to be received in chronological order.
  6276.      */
  6277.     addPoint: function(timeTicks, value) {
  6278.       var time = timeutil.convertTimeTicksToDate(timeTicks).getTime();
  6279.       this.dataPoints_.push(new DataPoint(time, value));
  6280.     },
  6281.  
  6282.     isVisible: function() {
  6283.       return this.isVisible_;
  6284.     },
  6285.  
  6286.     show: function(isVisible) {
  6287.       this.isVisible_ = isVisible;
  6288.     },
  6289.  
  6290.     getColor: function() {
  6291.       return this.color_;
  6292.     },
  6293.  
  6294.     setColor: function(color) {
  6295.       this.color_ = color;
  6296.     },
  6297.  
  6298.     getDataType: function() {
  6299.       return this.dataType_;
  6300.     },
  6301.  
  6302.     /**
  6303.      * Returns a list containing the values of the data series at |count|
  6304.      * points, starting at |startTime|, and |stepSize| milliseconds apart.
  6305.      * Caches values, so showing/hiding individual data series is fast, and
  6306.      * derived data series can be efficiently computed, if we add any.
  6307.      */
  6308.     getValues: function(startTime, stepSize, count) {
  6309.       // Use cached values, if we can.
  6310.       if (this.cacheStartTime_ == startTime &&
  6311.           this.cacheStepSize_ == stepSize &&
  6312.           this.cacheValues_.length == count) {
  6313.         return this.cacheValues_;
  6314.       }
  6315.  
  6316.       // Do all the work.
  6317.       this.cacheValues_ = this.getValuesInternal_(startTime, stepSize, count);
  6318.       this.cacheStartTime_ = startTime;
  6319.       this.cacheStepSize_ = stepSize;
  6320.  
  6321.       return this.cacheValues_;
  6322.     },
  6323.  
  6324.     /**
  6325.      * Does all the work of getValues when we can't use cached data.
  6326.      *
  6327.      * The default implementation just uses the |value| of the most recently
  6328.      * seen DataPoint before each time, but other DataSeries may use some
  6329.      * form of interpolation.
  6330.      * TODO(mmenke):  Consider returning the maximum value over each interval
  6331.      *                to create graphs more stable with respect to zooming.
  6332.      */
  6333.     getValuesInternal_: function(startTime, stepSize, count) {
  6334.       var values = [];
  6335.       var nextPoint = 0;
  6336.       var currentValue = 0;
  6337.       var time = startTime;
  6338.       for (var i = 0; i < count; ++i) {
  6339.         while (nextPoint < this.dataPoints_.length &&
  6340.                this.dataPoints_[nextPoint].time < time) {
  6341.           currentValue = this.dataPoints_[nextPoint].value;
  6342.           ++nextPoint;
  6343.         }
  6344.         values[i] = currentValue;
  6345.         time += stepSize;
  6346.       }
  6347.       return values;
  6348.     }
  6349.   };
  6350.  
  6351.   /**
  6352.    * A single point in a data series.  Each point has a time, in the form of
  6353.    * milliseconds since the Unix epoch, and a numeric value.
  6354.    * @constructor
  6355.    */
  6356.   function DataPoint(time, value) {
  6357.     this.time = time;
  6358.     this.value = value;
  6359.   }
  6360.  
  6361.   return TimelineDataSeries;
  6362. })();
  6363.  
  6364. /**
  6365.  * Tracks how many sources of the given type have seen a begin
  6366.  * event of type |eventType| more recently than an end event.
  6367.  */
  6368. var SourceCountDataSeries = (function() {
  6369.   'use strict';
  6370.  
  6371.   var superClass = TimelineDataSeries;
  6372.  
  6373.   /**
  6374.    * @constructor
  6375.    */
  6376.   function SourceCountDataSeries(sourceType, eventType) {
  6377.     superClass.call(this, TimelineDataType.SOURCE_COUNT);
  6378.     this.sourceType_ = sourceType;
  6379.     this.eventType_ = eventType;
  6380.  
  6381.     // Map of sources for which we've seen a begin event more recently than an
  6382.     // end event.  Each such source has a value of "true".  All others are
  6383.     // undefined.
  6384.     this.activeSources_ = {};
  6385.     // Number of entries in |activeSources_|.
  6386.     this.activeCount_ = 0;
  6387.   }
  6388.  
  6389.   SourceCountDataSeries.prototype = {
  6390.     // Inherit the superclass's methods.
  6391.     __proto__: superClass.prototype,
  6392.  
  6393.     onReceivedLogEntry: function(entry) {
  6394.       if (entry.source.type != this.sourceType_ ||
  6395.           entry.type != this.eventType_) {
  6396.         return;
  6397.       }
  6398.  
  6399.       if (entry.phase == EventPhase.PHASE_BEGIN) {
  6400.         this.onBeginEvent(entry.source.id, entry.time);
  6401.         return;
  6402.       }
  6403.       if (entry.phase == EventPhase.PHASE_END)
  6404.         this.onEndEvent(entry.source.id, entry.time);
  6405.     },
  6406.  
  6407.     /**
  6408.      * Called when the source with the specified id begins doing whatever we
  6409.      * care about.  If it's not already an active source, we add it to the map
  6410.      * and add a data point.
  6411.      */
  6412.     onBeginEvent: function(id, time) {
  6413.       if (this.activeSources_[id])
  6414.         return;
  6415.       this.activeSources_[id] = true;
  6416.       ++this.activeCount_;
  6417.       this.addPoint(time, this.activeCount_);
  6418.     },
  6419.  
  6420.     /**
  6421.      * Called when the source with the specified id stops doing whatever we
  6422.      * care about.  If it's an active source, we remove it from the map and add
  6423.      * a data point.
  6424.      */
  6425.     onEndEvent: function(id, time) {
  6426.       if (!this.activeSources_[id])
  6427.         return;
  6428.       delete this.activeSources_[id];
  6429.       --this.activeCount_;
  6430.       this.addPoint(time, this.activeCount_);
  6431.     }
  6432.   };
  6433.  
  6434.   return SourceCountDataSeries;
  6435. })();
  6436.  
  6437. /**
  6438.  * Tracks the number of sockets currently in use.  Needs special handling of
  6439.  * SSL sockets, so can't just use a normal SourceCountDataSeries.
  6440.  */
  6441. var SocketsInUseDataSeries = (function() {
  6442.   'use strict';
  6443.  
  6444.   var superClass = SourceCountDataSeries;
  6445.  
  6446.   /**
  6447.    * @constructor
  6448.    */
  6449.   function SocketsInUseDataSeries() {
  6450.     superClass.call(this, EventSourceType.SOCKET, EventType.SOCKET_IN_USE);
  6451.   }
  6452.  
  6453.   SocketsInUseDataSeries.prototype = {
  6454.     // Inherit the superclass's methods.
  6455.     __proto__: superClass.prototype,
  6456.  
  6457.     onReceivedLogEntry: function(entry) {
  6458.       // SSL sockets have two nested SOCKET_IN_USE events.  This is needed to
  6459.       // mark SSL sockets as unused after SSL negotiation.
  6460.       if (entry.type == EventType.SSL_CONNECT &&
  6461.           entry.phase == EventPhase.PHASE_END) {
  6462.         this.onEndEvent(entry.source.id, entry.time);
  6463.         return;
  6464.       }
  6465.       superClass.prototype.onReceivedLogEntry.call(this, entry);
  6466.     }
  6467.   };
  6468.  
  6469.   return SocketsInUseDataSeries;
  6470. })();
  6471.  
  6472. /**
  6473.  * Tracks approximate data rate using individual data transfer events.
  6474.  * Abstract class, doesn't implement onReceivedLogEntry.
  6475.  */
  6476. var TransferRateDataSeries = (function() {
  6477.   'use strict';
  6478.  
  6479.   var superClass = TimelineDataSeries;
  6480.  
  6481.   /**
  6482.    * @constructor
  6483.    */
  6484.   function TransferRateDataSeries() {
  6485.     superClass.call(this, TimelineDataType.BYTES_PER_SECOND);
  6486.   }
  6487.  
  6488.   TransferRateDataSeries.prototype = {
  6489.     // Inherit the superclass's methods.
  6490.     __proto__: superClass.prototype,
  6491.  
  6492.     /**
  6493.      * Returns the average data rate over each interval, only taking into
  6494.      * account transfers that occurred within each interval.
  6495.      * TODO(mmenke): Do something better.
  6496.      */
  6497.     getValuesInternal_: function(startTime, stepSize, count) {
  6498.       // Find the first DataPoint after |startTime| - |stepSize|.
  6499.       var nextPoint = 0;
  6500.       while (nextPoint < this.dataPoints_.length &&
  6501.              this.dataPoints_[nextPoint].time < startTime - stepSize) {
  6502.         ++nextPoint;
  6503.       }
  6504.  
  6505.       var values = [];
  6506.       var time = startTime;
  6507.       for (var i = 0; i < count; ++i) {
  6508.         // Calculate total bytes transferred from |time| - |stepSize|
  6509.         // to |time|.  We look at the transfers before |time| to give
  6510.         // us generally non-varying values for a given time.
  6511.         var transferred = 0;
  6512.         while (nextPoint < this.dataPoints_.length &&
  6513.                this.dataPoints_[nextPoint].time < time) {
  6514.           transferred += this.dataPoints_[nextPoint].value;
  6515.           ++nextPoint;
  6516.         }
  6517.         // Calculate bytes per second.
  6518.         values[i] = 1000 * transferred / stepSize;
  6519.         time += stepSize;
  6520.       }
  6521.       return values;
  6522.     }
  6523.   };
  6524.  
  6525.   return TransferRateDataSeries;
  6526. })();
  6527.  
  6528. /**
  6529.  * Tracks TCP and UDP transfer rate.
  6530.  */
  6531. var NetworkTransferRateDataSeries = (function() {
  6532.   'use strict';
  6533.  
  6534.   var superClass = TransferRateDataSeries;
  6535.  
  6536.   /**
  6537.    * |tcpEvent| and |udpEvent| are the event types for data transfers using
  6538.    * TCP and UDP, respectively.
  6539.    * @constructor
  6540.    */
  6541.   function NetworkTransferRateDataSeries(tcpEvent, udpEvent) {
  6542.     superClass.call(this);
  6543.     this.tcpEvent_ = tcpEvent;
  6544.     this.udpEvent_ = udpEvent;
  6545.   }
  6546.  
  6547.   NetworkTransferRateDataSeries.prototype = {
  6548.     // Inherit the superclass's methods.
  6549.     __proto__: superClass.prototype,
  6550.  
  6551.     onReceivedLogEntry: function(entry) {
  6552.       if (entry.type != this.tcpEvent_ && entry.type != this.udpEvent_)
  6553.         return;
  6554.       this.addPoint(entry.time, entry.params.byte_count);
  6555.     },
  6556.   };
  6557.  
  6558.   return NetworkTransferRateDataSeries;
  6559. })();
  6560.  
  6561. /**
  6562.  * Tracks disk cache read or write rate.  Doesn't include clearing, opening,
  6563.  * or dooming entries, as they don't have clear size values.
  6564.  */
  6565. var DiskCacheTransferRateDataSeries = (function() {
  6566.   'use strict';
  6567.  
  6568.   var superClass = TransferRateDataSeries;
  6569.  
  6570.   /**
  6571.    * @constructor
  6572.    */
  6573.   function DiskCacheTransferRateDataSeries(eventType) {
  6574.     superClass.call(this);
  6575.     this.eventType_ = eventType;
  6576.   }
  6577.  
  6578.   DiskCacheTransferRateDataSeries.prototype = {
  6579.     // Inherit the superclass's methods.
  6580.     __proto__: superClass.prototype,
  6581.  
  6582.     onReceivedLogEntry: function(entry) {
  6583.       if (entry.source.type != EventSourceType.DISK_CACHE_ENTRY ||
  6584.           entry.type != this.eventType_ ||
  6585.           entry.phase != EventPhase.PHASE_END) {
  6586.         return;
  6587.       }
  6588.       // The disk cache has a lot of 0-length writes, when truncating entries.
  6589.       // Ignore those.
  6590.       if (entry.params.bytes_copied != 0)
  6591.         this.addPoint(entry.time, entry.params.bytes_copied);
  6592.     }
  6593.   };
  6594.  
  6595.   return DiskCacheTransferRateDataSeries;
  6596. })();
  6597.  
  6598. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  6599. // Use of this source code is governed by a BSD-style license that can be
  6600. // found in the LICENSE file.
  6601.  
  6602. /**
  6603.  * A TimelineGraphView displays a timeline graph on a canvas element.
  6604.  */
  6605. var TimelineGraphView = (function() {
  6606.   'use strict';
  6607.   // We inherit from TopMidBottomView.
  6608.   var superClass = TopMidBottomView;
  6609.  
  6610.   // Default starting scale factor, in terms of milliseconds per pixel.
  6611.   var DEFAULT_SCALE = 1000;
  6612.  
  6613.   // Maximum number of labels placed vertically along the sides of the graph.
  6614.   var MAX_VERTICAL_LABELS = 6;
  6615.  
  6616.   // Vertical spacing between labels and between the graph and labels.
  6617.   var LABEL_VERTICAL_SPACING = 4;
  6618.   // Horizontal spacing between vertically placed labels and the edges of the
  6619.   // graph.
  6620.   var LABEL_HORIZONTAL_SPACING = 3;
  6621.   // Horizintal spacing between two horitonally placed labels along the bottom
  6622.   // of the graph.
  6623.   var LABEL_LABEL_HORIZONTAL_SPACING = 25;
  6624.  
  6625.   // Length of ticks, in pixels, next to y-axis labels.  The x-axis only has
  6626.   // one set of labels, so it can use lines instead.
  6627.   var Y_AXIS_TICK_LENGTH = 10;
  6628.  
  6629.   // The number of units mouse wheel deltas increase for each tick of the
  6630.   // wheel.
  6631.   var MOUSE_WHEEL_UNITS_PER_CLICK = 120;
  6632.  
  6633.   // Amount we zoom for one vertical tick of the mouse wheel, as a ratio.
  6634.   var MOUSE_WHEEL_ZOOM_RATE = 1.25;
  6635.   // Amount we scroll for one horizontal tick of the mouse wheel, in pixels.
  6636.   var MOUSE_WHEEL_SCROLL_RATE = MOUSE_WHEEL_UNITS_PER_CLICK;
  6637.   // Number of pixels to scroll per pixel the mouse is dragged.
  6638.   var MOUSE_WHEEL_DRAG_RATE = 3;
  6639.  
  6640.   var GRID_COLOR = '#CCC';
  6641.   var TEXT_COLOR = '#000';
  6642.   var BACKGROUND_COLOR = '#FFF';
  6643.  
  6644.   // Which side of the canvas y-axis labels should go on, for a given Graph.
  6645.   // TODO(mmenke):  Figure out a reasonable way to handle more than 2 sets
  6646.   //                of labels.
  6647.   var LabelAlign = {
  6648.     LEFT: 0,
  6649.     RIGHT: 1
  6650.   };
  6651.  
  6652.   /**
  6653.    * @constructor
  6654.    */
  6655.   function TimelineGraphView(divId, canvasId, scrollbarId, scrollbarInnerId) {
  6656.     this.scrollbar_ = new HorizontalScrollbarView(scrollbarId,
  6657.                                                   scrollbarInnerId,
  6658.                                                   this.onScroll_.bind(this));
  6659.     // Call superclass's constructor.
  6660.     superClass.call(this, null, new DivView(divId), this.scrollbar_);
  6661.  
  6662.     this.graphDiv_ = $(divId);
  6663.     this.canvas_ = $(canvasId);
  6664.     this.canvas_.onmousewheel = this.onMouseWheel_.bind(this);
  6665.     this.canvas_.onmousedown = this.onMouseDown_.bind(this);
  6666.     this.canvas_.onmousemove = this.onMouseMove_.bind(this);
  6667.     this.canvas_.onmouseup = this.onMouseUp_.bind(this);
  6668.     this.canvas_.onmouseout = this.onMouseUp_.bind(this);
  6669.  
  6670.     // Used for click and drag scrolling of graph.  Drag-zooming not supported,
  6671.     // for a more stable scrolling experience.
  6672.     this.isDragging_ = false;
  6673.     this.dragX_ = 0;
  6674.  
  6675.     // Set the range and scale of the graph.  Times are in milliseconds since
  6676.     // the Unix epoch.
  6677.  
  6678.     // All measurements we have must be after this time.
  6679.     this.startTime_ = 0;
  6680.     // The current rightmost position of the graph is always at most this.
  6681.     // We may have some later events.  When actively capturing new events, it's
  6682.     // updated on a timer.
  6683.     this.endTime_ = 1;
  6684.  
  6685.     // Current scale, in terms of milliseconds per pixel.  Each column of
  6686.     // pixels represents a point in time |scale_| milliseconds after the
  6687.     // previous one.  We only display times that are of the form
  6688.     // |startTime_| + K * |scale_| to avoid jittering, and the rightmost
  6689.     // pixel that we can display has a time <= |endTime_|.  Non-integer values
  6690.     // are allowed.
  6691.     this.scale_ = DEFAULT_SCALE;
  6692.  
  6693.     this.graphs_ = [];
  6694.  
  6695.     // Initialize the scrollbar.
  6696.     this.updateScrollbarRange_(true);
  6697.   }
  6698.  
  6699.   // Smallest allowed scaling factor.
  6700.   TimelineGraphView.MIN_SCALE = 5;
  6701.  
  6702.   TimelineGraphView.prototype = {
  6703.     // Inherit the superclass's methods.
  6704.     __proto__: superClass.prototype,
  6705.  
  6706.     setGeometry: function(left, top, width, height) {
  6707.       superClass.prototype.setGeometry.call(this, left, top, width, height);
  6708.  
  6709.       // The size of the canvas can only be set by using its |width| and
  6710.       // |height| properties, which do not take padding into account, so we
  6711.       // need to use them ourselves.
  6712.       var style = getComputedStyle(this.canvas_);
  6713.       var horizontalPadding = parseInt(style.paddingRight) +
  6714.                                   parseInt(style.paddingLeft);
  6715.       var verticalPadding = parseInt(style.paddingTop) +
  6716.                                 parseInt(style.paddingBottom);
  6717.       var canvasWidth =
  6718.           parseInt(this.graphDiv_.style.width) - horizontalPadding;
  6719.       // For unknown reasons, there's an extra 3 pixels border between the
  6720.       // bottom of the canvas and the bottom margin of the enclosing div.
  6721.       var canvasHeight =
  6722.           parseInt(this.graphDiv_.style.height) - verticalPadding - 3;
  6723.  
  6724.       // Protect against degenerates.
  6725.       if (canvasWidth < 10)
  6726.         canvasWidth = 10;
  6727.       if (canvasHeight < 10)
  6728.         canvasHeight = 10;
  6729.  
  6730.       this.canvas_.width = canvasWidth;
  6731.       this.canvas_.height = canvasHeight;
  6732.  
  6733.       // Use the same font style for the canvas as we use elsewhere.
  6734.       // Has to be updated every resize.
  6735.       this.canvas_.getContext('2d').font = getComputedStyle(this.canvas_).font;
  6736.  
  6737.       this.updateScrollbarRange_(this.graphScrolledToRightEdge_());
  6738.       this.repaint();
  6739.     },
  6740.  
  6741.     show: function(isVisible) {
  6742.       superClass.prototype.show.call(this, isVisible);
  6743.       if (isVisible)
  6744.         this.repaint();
  6745.     },
  6746.  
  6747.     // Returns the total length of the graph, in pixels.
  6748.     getLength_: function() {
  6749.       var timeRange = this.endTime_ - this.startTime_;
  6750.       // Math.floor is used to ignore the last partial area, of length less
  6751.       // than |scale_|.
  6752.       return Math.floor(timeRange / this.scale_);
  6753.     },
  6754.  
  6755.     /**
  6756.      * Returns true if the graph is scrolled all the way to the right.
  6757.      */
  6758.     graphScrolledToRightEdge_: function() {
  6759.       return this.scrollbar_.getPosition() == this.scrollbar_.getRange();
  6760.     },
  6761.  
  6762.     /**
  6763.      * Update the range of the scrollbar.  If |resetPosition| is true, also
  6764.      * sets the slider to point at the rightmost position and triggers a
  6765.      * repaint.
  6766.      */
  6767.     updateScrollbarRange_: function(resetPosition) {
  6768.       var scrollbarRange = this.getLength_() - this.canvas_.width;
  6769.       if (scrollbarRange < 0)
  6770.         scrollbarRange = 0;
  6771.  
  6772.       // If we've decreased the range to less than the current scroll position,
  6773.       // we need to move the scroll position.
  6774.       if (this.scrollbar_.getPosition() > scrollbarRange)
  6775.         resetPosition = true;
  6776.  
  6777.       this.scrollbar_.setRange(scrollbarRange);
  6778.       if (resetPosition) {
  6779.         this.scrollbar_.setPosition(scrollbarRange);
  6780.         this.repaint();
  6781.       }
  6782.     },
  6783.  
  6784.     /**
  6785.      * Sets the date range displayed on the graph, switches to the default
  6786.      * scale factor, and moves the scrollbar all the way to the right.
  6787.      */
  6788.     setDateRange: function(startDate, endDate) {
  6789.       this.startTime_ = startDate.getTime();
  6790.       this.endTime_ = endDate.getTime();
  6791.  
  6792.       // Safety check.
  6793.       if (this.endTime_ <= this.startTime_)
  6794.         this.startTime_ = this.endTime_ - 1;
  6795.  
  6796.       this.scale_ = DEFAULT_SCALE;
  6797.       this.updateScrollbarRange_(true);
  6798.     },
  6799.  
  6800.     /**
  6801.      * Updates the end time at the right of the graph to be the current time.
  6802.      * Specifically, updates the scrollbar's range, and if the scrollbar is
  6803.      * all the way to the right, keeps it all the way to the right.  Otherwise,
  6804.      * leaves the view as-is and doesn't redraw anything.
  6805.      */
  6806.     updateEndDate: function() {
  6807.       this.endTime_ = timeutil.getCurrentTime();
  6808.       this.updateScrollbarRange_(this.graphScrolledToRightEdge_());
  6809.     },
  6810.  
  6811.     getStartDate: function() {
  6812.       return new Date(this.startTime_);
  6813.     },
  6814.  
  6815.     /**
  6816.      * Scrolls the graph horizontally by the specified amount.
  6817.      */
  6818.     horizontalScroll_: function(delta) {
  6819.       var newPosition = this.scrollbar_.getPosition() + Math.round(delta);
  6820.       // Make sure the new position is in the right range.
  6821.       if (newPosition < 0) {
  6822.         newPosition = 0;
  6823.       } else if (newPosition > this.scrollbar_.getRange()) {
  6824.         newPosition = this.scrollbar_.getRange();
  6825.       }
  6826.  
  6827.       if (this.scrollbar_.getPosition() == newPosition)
  6828.         return;
  6829.       this.scrollbar_.setPosition(newPosition);
  6830.       this.onScroll_();
  6831.     },
  6832.  
  6833.     /**
  6834.      * Zooms the graph by the specified amount.
  6835.      */
  6836.     zoom_: function(ratio) {
  6837.       var oldScale = this.scale_;
  6838.       this.scale_ *= ratio;
  6839.       if (this.scale_ < TimelineGraphView.MIN_SCALE)
  6840.         this.scale_ = TimelineGraphView.MIN_SCALE;
  6841.  
  6842.       if (this.scale_ == oldScale)
  6843.         return;
  6844.  
  6845.       // If we were at the end of the range before, remain at the end of the
  6846.       // range.
  6847.       if (this.graphScrolledToRightEdge_()) {
  6848.         this.updateScrollbarRange_(true);
  6849.         return;
  6850.       }
  6851.  
  6852.       // Otherwise, do our best to maintain the old position.  We use the
  6853.       // position at the far right of the graph for consistency.
  6854.       var oldMaxTime =
  6855.           oldScale * (this.scrollbar_.getPosition() + this.canvas_.width);
  6856.       var newMaxTime = Math.round(oldMaxTime / this.scale_);
  6857.       var newPosition = newMaxTime - this.canvas_.width;
  6858.  
  6859.       // Update range and scroll position.
  6860.       this.updateScrollbarRange_(false);
  6861.       this.horizontalScroll_(newPosition - this.scrollbar_.getPosition());
  6862.     },
  6863.  
  6864.     onMouseWheel_: function(event) {
  6865.       event.preventDefault();
  6866.       this.horizontalScroll_(
  6867.           MOUSE_WHEEL_SCROLL_RATE *
  6868.               -event.wheelDeltaX / MOUSE_WHEEL_UNITS_PER_CLICK);
  6869.       this.zoom_(Math.pow(MOUSE_WHEEL_ZOOM_RATE,
  6870.                  -event.wheelDeltaY / MOUSE_WHEEL_UNITS_PER_CLICK));
  6871.     },
  6872.  
  6873.     onMouseDown_: function(event) {
  6874.       event.preventDefault();
  6875.       this.isDragging_ = true;
  6876.       this.dragX_ = event.clientX;
  6877.     },
  6878.  
  6879.     onMouseMove_: function(event) {
  6880.       if (!this.isDragging_)
  6881.         return;
  6882.       event.preventDefault();
  6883.       this.horizontalScroll_(
  6884.           MOUSE_WHEEL_DRAG_RATE * (event.clientX - this.dragX_));
  6885.       this.dragX_ = event.clientX;
  6886.     },
  6887.  
  6888.     onMouseUp_: function(event) {
  6889.       this.isDragging_ = false;
  6890.     },
  6891.  
  6892.     onScroll_: function() {
  6893.       this.repaint();
  6894.     },
  6895.  
  6896.     /**
  6897.      * Replaces the current TimelineDataSeries with |dataSeries|.
  6898.      */
  6899.     setDataSeries: function(dataSeries) {
  6900.       // Simplest just to recreate the Graphs.
  6901.       this.graphs_ = [];
  6902.       this.graphs_[TimelineDataType.BYTES_PER_SECOND] =
  6903.           new Graph(TimelineDataType.BYTES_PER_SECOND, LabelAlign.RIGHT);
  6904.       this.graphs_[TimelineDataType.SOURCE_COUNT] =
  6905.           new Graph(TimelineDataType.SOURCE_COUNT, LabelAlign.LEFT);
  6906.       for (var i = 0; i < dataSeries.length; ++i)
  6907.         this.graphs_[dataSeries[i].getDataType()].addDataSeries(dataSeries[i]);
  6908.  
  6909.       this.repaint();
  6910.     },
  6911.  
  6912.     /**
  6913.      * Draws the graph on |canvas_|.
  6914.      */
  6915.     repaint: function() {
  6916.       this.repaintTimerRunning_ = false;
  6917.       if (!this.isVisible())
  6918.         return;
  6919.  
  6920.       var width = this.canvas_.width;
  6921.       var height = this.canvas_.height;
  6922.       var context = this.canvas_.getContext('2d');
  6923.  
  6924.       // Clear the canvas.
  6925.       context.fillStyle = BACKGROUND_COLOR;
  6926.       context.fillRect(0, 0, width, height);
  6927.  
  6928.       // Try to get font height in pixels.  Needed for layout.
  6929.       var fontHeightString = context.font.match(/([0-9]+)px/)[1];
  6930.       var fontHeight = parseInt(fontHeightString);
  6931.  
  6932.       // Safety check, to avoid drawing anything too ugly.
  6933.       if (fontHeightString.length == 0 || fontHeight <= 0 ||
  6934.           fontHeight * 4 > height || width < 50) {
  6935.         return;
  6936.       }
  6937.  
  6938.       // Save current transformation matrix so we can restore it later.
  6939.       context.save();
  6940.  
  6941.       // The center of an HTML canvas pixel is technically at (0.5, 0.5).  This
  6942.       // makes near straight lines look bad, due to anti-aliasing.  This
  6943.       // translation reduces the problem a little.
  6944.       context.translate(0.5, 0.5);
  6945.  
  6946.       // Figure out what time values to display.
  6947.       var position = this.scrollbar_.getPosition();
  6948.       // If the entire time range is being displayed, align the right edge of
  6949.       // the graph to the end of the time range.
  6950.       if (this.scrollbar_.getRange() == 0)
  6951.         position = this.getLength_() - this.canvas_.width;
  6952.       var visibleStartTime = this.startTime_ + position * this.scale_;
  6953.  
  6954.       // Make space at the bottom of the graph for the time labels, and then
  6955.       // draw the labels.
  6956.       var textHeight = height;
  6957.       height -= fontHeight + LABEL_VERTICAL_SPACING;
  6958.       this.drawTimeLabels(context, width, height, textHeight, visibleStartTime);
  6959.  
  6960.       // Draw outline of the main graph area.
  6961.       context.strokeStyle = GRID_COLOR;
  6962.       context.strokeRect(0, 0, width - 1, height - 1);
  6963.  
  6964.       // Layout graphs and have them draw their tick marks.
  6965.       for (var i = 0; i < this.graphs_.length; ++i) {
  6966.         this.graphs_[i].layout(width, height, fontHeight, visibleStartTime,
  6967.                                this.scale_);
  6968.         this.graphs_[i].drawTicks(context);
  6969.       }
  6970.  
  6971.       // Draw the lines of all graphs, and then draw their labels.
  6972.       for (var i = 0; i < this.graphs_.length; ++i)
  6973.         this.graphs_[i].drawLines(context);
  6974.       for (var i = 0; i < this.graphs_.length; ++i)
  6975.         this.graphs_[i].drawLabels(context);
  6976.  
  6977.       // Restore original transformation matrix.
  6978.       context.restore();
  6979.     },
  6980.  
  6981.     /**
  6982.      * Draw time labels below the graph.  Takes in start time as an argument
  6983.      * since it may not be |startTime_|, when we're displaying the entire
  6984.      * time range.
  6985.      */
  6986.     drawTimeLabels: function(context, width, height, textHeight, startTime) {
  6987.       // Text for a time string to use in determining how far apart
  6988.       // to place text labels.
  6989.       var sampleText = (new Date(startTime)).toLocaleTimeString();
  6990.  
  6991.       // The desired spacing for text labels.
  6992.       var targetSpacing = context.measureText(sampleText).width +
  6993.                               LABEL_LABEL_HORIZONTAL_SPACING;
  6994.  
  6995.       // The allowed time step values between adjacent labels.  Anything much
  6996.       // over a couple minutes isn't terribly realistic, given how much memory
  6997.       // we use, and how slow a lot of the net-internals code is.
  6998.       var timeStepValues = [
  6999.         1000,  // 1 second
  7000.         1000 * 5,
  7001.         1000 * 30,
  7002.         1000 * 60,  // 1 minute
  7003.         1000 * 60 * 5,
  7004.         1000 * 60 * 30,
  7005.         1000 * 60 * 60,  // 1 hour
  7006.         1000 * 60 * 60 * 5
  7007.       ];
  7008.  
  7009.       // Find smallest time step value that gives us at least |targetSpacing|,
  7010.       // if any.
  7011.       var timeStep = null;
  7012.       for (var i = 0; i < timeStepValues.length; ++i) {
  7013.         if (timeStepValues[i] / this.scale_ >= targetSpacing) {
  7014.           timeStep = timeStepValues[i];
  7015.           break;
  7016.         }
  7017.       }
  7018.  
  7019.       // If no such value, give up.
  7020.       if (!timeStep)
  7021.         return;
  7022.  
  7023.       // Find the time for the first label.  This time is a perfect multiple of
  7024.       // timeStep because of how UTC times work.
  7025.       var time = Math.ceil(startTime / timeStep) * timeStep;
  7026.  
  7027.       context.textBaseline = 'bottom';
  7028.       context.textAlign = 'center';
  7029.       context.fillStyle = TEXT_COLOR;
  7030.       context.strokeStyle = GRID_COLOR;
  7031.  
  7032.       // Draw labels and vertical grid lines.
  7033.       while (true) {
  7034.         var x = Math.round((time - startTime) / this.scale_);
  7035.         if (x >= width)
  7036.           break;
  7037.         var text = (new Date(time)).toLocaleTimeString();
  7038.         context.fillText(text, x, textHeight);
  7039.         context.beginPath();
  7040.         context.lineTo(x, 0);
  7041.         context.lineTo(x, height);
  7042.         context.stroke();
  7043.         time += timeStep;
  7044.       }
  7045.     }
  7046.   };
  7047.  
  7048.   /**
  7049.    * A Graph is responsible for drawing all the TimelineDataSeries that have
  7050.    * the same data type.  Graphs are responsible for scaling the values, laying
  7051.    * out labels, and drawing both labels and lines for its data series.
  7052.    */
  7053.   var Graph = (function() {
  7054.     /**
  7055.      * |dataType| is the DataType that will be shared by all its DataSeries.
  7056.      * |labelAlign| is the LabelAlign value indicating whether the labels
  7057.      * should be aligned to the right of left of the graph.
  7058.      * @constructor
  7059.      */
  7060.     function Graph(dataType, labelAlign) {
  7061.       this.dataType_ = dataType;
  7062.       this.dataSeries_ = [];
  7063.       this.labelAlign_ = labelAlign;
  7064.  
  7065.       // Cached properties of the graph, set in layout.
  7066.       this.width_ = 0;
  7067.       this.height_ = 0;
  7068.       this.fontHeight_ = 0;
  7069.       this.startTime_ = 0;
  7070.       this.scale_ = 0;
  7071.  
  7072.       // At least the highest value in the displayed range of the graph.
  7073.       // Used for scaling and setting labels.  Set in layoutLabels.
  7074.       this.max_ = 0;
  7075.  
  7076.       // Cached text of equally spaced labels.  Set in layoutLabels.
  7077.       this.labels_ = [];
  7078.     }
  7079.  
  7080.     /**
  7081.      * A Label is the label at a particular position along the y-axis.
  7082.      * @constructor
  7083.      */
  7084.     function Label(height, text) {
  7085.       this.height = height;
  7086.       this.text = text;
  7087.     }
  7088.  
  7089.     Graph.prototype = {
  7090.       addDataSeries: function(dataSeries) {
  7091.         this.dataSeries_.push(dataSeries);
  7092.       },
  7093.  
  7094.       /**
  7095.        * Returns a list of all the values that should be displayed for a given
  7096.        * data series, using the current graph layout.
  7097.        */
  7098.       getValues: function(dataSeries) {
  7099.         if (!dataSeries.isVisible())
  7100.           return null;
  7101.         return dataSeries.getValues(this.startTime_, this.scale_, this.width_);
  7102.       },
  7103.  
  7104.       /**
  7105.        * Updates the graph's layout.  In particular, both the max value and
  7106.        * label positions are updated.  Must be called before calling any of the
  7107.        * drawing functions.
  7108.        */
  7109.       layout: function(width, height, fontHeight, startTime, scale) {
  7110.         this.width_ = width;
  7111.         this.height_ = height;
  7112.         this.fontHeight_ = fontHeight;
  7113.         this.startTime_ = startTime;
  7114.         this.scale_ = scale;
  7115.  
  7116.         // Find largest value.
  7117.         var max = 0;
  7118.         for (var i = 0; i < this.dataSeries_.length; ++i) {
  7119.           var values = this.getValues(this.dataSeries_[i]);
  7120.           if (!values)
  7121.             continue;
  7122.           for (var j = 0; j < values.length; ++j) {
  7123.             if (values[j] > max)
  7124.               max = values[j];
  7125.           }
  7126.         }
  7127.  
  7128.         this.layoutLabels_(max);
  7129.       },
  7130.  
  7131.       /**
  7132.        * Lays out labels and sets |max_|, taking the time units into
  7133.        * consideration.  |maxValue| is the actual maximum value, and
  7134.        * |max_| will be set to the value of the largest label, which
  7135.        * will be at least |maxValue|.
  7136.        */
  7137.       layoutLabels_: function(maxValue) {
  7138.         if (this.dataType_ != TimelineDataType.BYTES_PER_SECOND) {
  7139.           this.layoutLabelsBasic_(maxValue, 0);
  7140.           return;
  7141.         }
  7142.  
  7143.         // Special handling for data rates.
  7144.  
  7145.         // Find appropriate units to use.
  7146.         var units = ['B/s', 'kB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s'];
  7147.         // Units to use for labels.  0 is bytes, 1 is kilobytes, etc.
  7148.         // We start with kilobytes, and work our way up.
  7149.         var unit = 1;
  7150.         // Update |maxValue| to be in the right units.
  7151.         maxValue = maxValue / 1024;
  7152.         while (units[unit + 1] && maxValue >= 999) {
  7153.           maxValue /= 1024;
  7154.           ++unit;
  7155.         }
  7156.  
  7157.         // Calculate labels.
  7158.         this.layoutLabelsBasic_(maxValue, 1);
  7159.  
  7160.         // Append units to labels.
  7161.         for (var i = 0; i < this.labels_.length; ++i)
  7162.           this.labels_[i] += ' ' + units[unit];
  7163.  
  7164.         // Convert |max_| back to bytes, so it can be used when scaling values
  7165.         // for display.
  7166.         this.max_ *= Math.pow(1024, unit);
  7167.       },
  7168.  
  7169.       /**
  7170.        * Same as layoutLabels_, but ignores units.  |maxDecimalDigits| is the
  7171.        * maximum number of decimal digits allowed.  The minimum allowed
  7172.        * difference between two adjacent labels is 10^-|maxDecimalDigits|.
  7173.        */
  7174.       layoutLabelsBasic_: function(maxValue, maxDecimalDigits) {
  7175.         this.labels_ = [];
  7176.         // No labels if |maxValue| is 0.
  7177.         if (maxValue == 0) {
  7178.           this.max_ = maxValue;
  7179.           return;
  7180.         }
  7181.  
  7182.         // The maximum number of equally spaced labels allowed.  |fontHeight_|
  7183.         // is doubled because the top two labels are both drawn in the same
  7184.         // gap.
  7185.         var minLabelSpacing = 2 * this.fontHeight_ + LABEL_VERTICAL_SPACING;
  7186.  
  7187.         // The + 1 is for the top label.
  7188.         var maxLabels = 1 + this.height_ / minLabelSpacing;
  7189.         if (maxLabels < 2) {
  7190.           maxLabels = 2;
  7191.         } else if (maxLabels > MAX_VERTICAL_LABELS) {
  7192.           maxLabels = MAX_VERTICAL_LABELS;
  7193.         }
  7194.  
  7195.         // Initial try for step size between conecutive labels.
  7196.         var stepSize = Math.pow(10, -maxDecimalDigits);
  7197.         // Number of digits to the right of the decimal of |stepSize|.
  7198.         // Used for formating label strings.
  7199.         var stepSizeDecimalDigits = maxDecimalDigits;
  7200.  
  7201.         // Pick a reasonable step size.
  7202.         while (true) {
  7203.           // If we use a step size of |stepSize| between labels, we'll need:
  7204.           //
  7205.           // Math.ceil(maxValue / stepSize) + 1
  7206.           //
  7207.           // labels.  The + 1 is because we need labels at both at 0 and at
  7208.           // the top of the graph.
  7209.  
  7210.           // Check if we can use steps of size |stepSize|.
  7211.           if (Math.ceil(maxValue / stepSize) + 1 <= maxLabels)
  7212.             break;
  7213.           // Check |stepSize| * 2.
  7214.           if (Math.ceil(maxValue / (stepSize * 2)) + 1 <= maxLabels) {
  7215.             stepSize *= 2;
  7216.             break;
  7217.           }
  7218.           // Check |stepSize| * 5.
  7219.           if (Math.ceil(maxValue / (stepSize * 5)) + 1 <= maxLabels) {
  7220.             stepSize *= 5;
  7221.             break;
  7222.           }
  7223.           stepSize *= 10;
  7224.           if (stepSizeDecimalDigits > 0)
  7225.             --stepSizeDecimalDigits;
  7226.         }
  7227.  
  7228.         // Set the max so it's an exact multiple of the chosen step size.
  7229.         this.max_ = Math.ceil(maxValue / stepSize) * stepSize;
  7230.  
  7231.         // Create labels.
  7232.         for (var label = this.max_; label >= 0; label -= stepSize)
  7233.           this.labels_.push(label.toFixed(stepSizeDecimalDigits));
  7234.       },
  7235.  
  7236.       /**
  7237.        * Draws tick marks for each of the labels in |labels_|.
  7238.        */
  7239.       drawTicks: function(context) {
  7240.         var x1;
  7241.         var x2;
  7242.         if (this.labelAlign_ == LabelAlign.RIGHT) {
  7243.           x1 = this.width_ - 1;
  7244.           x2 = this.width_ - 1 - Y_AXIS_TICK_LENGTH;
  7245.         } else {
  7246.           x1 = 0;
  7247.           x2 = Y_AXIS_TICK_LENGTH;
  7248.         }
  7249.  
  7250.         context.fillStyle = GRID_COLOR;
  7251.         context.beginPath();
  7252.         for (var i = 1; i < this.labels_.length - 1; ++i) {
  7253.           // The rounding is needed to avoid ugly 2-pixel wide anti-aliased
  7254.           // lines.
  7255.           var y = Math.round(this.height_ * i / (this.labels_.length - 1));
  7256.           context.moveTo(x1, y);
  7257.           context.lineTo(x2, y);
  7258.         }
  7259.         context.stroke();
  7260.       },
  7261.  
  7262.       /**
  7263.        * Draws a graph line for each of the data series.
  7264.        */
  7265.       drawLines: function(context) {
  7266.         // Factor by which to scale all values to convert them to a number from
  7267.         // 0 to height - 1.
  7268.         var scale = 0;
  7269.         var bottom = this.height_ - 1;
  7270.         if (this.max_)
  7271.           scale = bottom / this.max_;
  7272.  
  7273.         // Draw in reverse order, so earlier data series are drawn on top of
  7274.         // subsequent ones.
  7275.         for (var i = this.dataSeries_.length - 1; i >= 0; --i) {
  7276.           var values = this.getValues(this.dataSeries_[i]);
  7277.           if (!values)
  7278.             continue;
  7279.           context.strokeStyle = this.dataSeries_[i].getColor();
  7280.           context.beginPath();
  7281.           for (var x = 0; x < values.length; ++x) {
  7282.             // The rounding is needed to avoid ugly 2-pixel wide anti-aliased
  7283.             // horizontal lines.
  7284.             context.lineTo(x, bottom - Math.round(values[x] * scale));
  7285.           }
  7286.           context.stroke();
  7287.         }
  7288.       },
  7289.  
  7290.       /**
  7291.        * Draw labels in |labels_|.
  7292.        */
  7293.       drawLabels: function(context) {
  7294.         if (this.labels_.length == 0)
  7295.           return;
  7296.         var x;
  7297.         if (this.labelAlign_ == LabelAlign.RIGHT) {
  7298.           x = this.width_ - LABEL_HORIZONTAL_SPACING;
  7299.         } else {
  7300.           // Find the width of the widest label.
  7301.           var maxTextWidth = 0;
  7302.           for (var i = 0; i < this.labels_.length; ++i) {
  7303.             var textWidth = context.measureText(this.labels_[i]).width;
  7304.             if (maxTextWidth < textWidth)
  7305.               maxTextWidth = textWidth;
  7306.           }
  7307.           x = maxTextWidth + LABEL_HORIZONTAL_SPACING;
  7308.         }
  7309.  
  7310.         // Set up the context.
  7311.         context.fillStyle = TEXT_COLOR;
  7312.         context.textAlign = 'right';
  7313.  
  7314.         // Draw top label, which is the only one that appears below its tick
  7315.         // mark.
  7316.         context.textBaseline = 'top';
  7317.         context.fillText(this.labels_[0], x, 0);
  7318.  
  7319.         // Draw all the other labels.
  7320.         context.textBaseline = 'bottom';
  7321.         var step = (this.height_ - 1) / (this.labels_.length - 1);
  7322.         for (var i = 1; i < this.labels_.length; ++i)
  7323.           context.fillText(this.labels_[i], x, step * i);
  7324.       }
  7325.     };
  7326.  
  7327.     return Graph;
  7328.   })();
  7329.  
  7330.   return TimelineGraphView;
  7331. })();
  7332.  
  7333. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  7334. // Use of this source code is governed by a BSD-style license that can be
  7335. // found in the LICENSE file.
  7336.  
  7337. /**
  7338.  * TimelineView displays a zoomable and scrollable graph of a number of values
  7339.  * over time.  The TimelineView class itself is responsible primarily for
  7340.  * updating the TimelineDataSeries its GraphView displays.
  7341.  */
  7342. var TimelineView = (function() {
  7343.   'use strict';
  7344.  
  7345.   // We inherit from HorizontalSplitView.
  7346.   var superClass = HorizontalSplitView;
  7347.  
  7348.   /**
  7349.    * @constructor
  7350.    */
  7351.   function TimelineView() {
  7352.     assertFirstConstructorCall(TimelineView);
  7353.  
  7354.     this.graphView_ = new TimelineGraphView(
  7355.         TimelineView.GRAPH_DIV_ID,
  7356.         TimelineView.GRAPH_CANVAS_ID,
  7357.         TimelineView.SCROLLBAR_DIV_ID,
  7358.         TimelineView.SCROLLBAR_INNER_DIV_ID);
  7359.  
  7360.     // Call superclass's constructor.
  7361.  
  7362.     var selectionView = new DivView(TimelineView.SELECTION_DIV_ID);
  7363.     superClass.call(this, selectionView, this.graphView_);
  7364.  
  7365.     this.selectionDivFullWidth_ = selectionView.getWidth();
  7366.     $(TimelineView.SELECTION_TOGGLE_ID).onclick =
  7367.         this.toggleSelectionDiv_.bind(this);
  7368.  
  7369.     // Interval id returned by window.setInterval for update timer.
  7370.     this.updateIntervalId_ = null;
  7371.  
  7372.     // List of DataSeries.  These are shared with the TimelineGraphView.  The
  7373.     // TimelineView updates their state, the TimelineGraphView reads their
  7374.     // state and draws them.
  7375.     this.dataSeries_ = [];
  7376.  
  7377.     // DataSeries depend on some of the global constants, so they're only
  7378.     // created once constants have been received.  We also use this message to
  7379.     // recreate DataSeries when log files are being loaded.
  7380.     g_browser.addConstantsObserver(this);
  7381.  
  7382.     // We observe new log entries to determine the range of the graph, and pass
  7383.     // them on to each DataSource.  We initialize the graph range to initially
  7384.     // include all events, but after that, we only update it to be the current
  7385.     // time on a timer.
  7386.     EventsTracker.getInstance().addLogEntryObserver(this);
  7387.     this.graphRangeInitialized_ = false;
  7388.   }
  7389.  
  7390.   // ID for special HTML element in category_tabs.html
  7391.   TimelineView.TAB_HANDLE_ID = 'tab-handle-timeline';
  7392.  
  7393.   // IDs for special HTML elements in timeline_view.html
  7394.   TimelineView.GRAPH_DIV_ID = 'timeline-view-graph-div';
  7395.   TimelineView.GRAPH_CANVAS_ID = 'timeline-view-graph-canvas';
  7396.   TimelineView.SELECTION_DIV_ID = 'timeline-view-selection-div';
  7397.   TimelineView.SELECTION_TOGGLE_ID = 'timeline-view-selection-toggle';
  7398.   TimelineView.SELECTION_UL_ID = 'timeline-view-selection-ul';
  7399.   TimelineView.SCROLLBAR_DIV_ID = 'timeline-view-scrollbar-div';
  7400.   TimelineView.SCROLLBAR_INNER_DIV_ID = 'timeline-view-scrollbar-inner-div';
  7401.  
  7402.   TimelineView.OPEN_SOCKETS_ID = 'timeline-view-open-sockets';
  7403.   TimelineView.IN_USE_SOCKETS_ID = 'timeline-view-in-use-sockets';
  7404.   TimelineView.URL_REQUESTS_ID = 'timeline-view-url-requests';
  7405.   TimelineView.DNS_REQUESTS_ID = 'timeline-view-dns-requests';
  7406.   TimelineView.DNS_JOBS_ID = 'timeline-view-dns-jobs';
  7407.   TimelineView.BYTES_RECEIVED_ID = 'timeline-view-bytes-received';
  7408.   TimelineView.BYTES_SENT_ID = 'timeline-view-bytes-sent';
  7409.   TimelineView.DISK_CACHE_BYTES_READ_ID =
  7410.       'timeline-view-disk-cache-bytes-read';
  7411.   TimelineView.DISK_CACHE_BYTES_WRITTEN_ID =
  7412.       'timeline-view-disk-cache-bytes-written';
  7413.  
  7414.   // Class used for hiding the colored squares next to the labels for the
  7415.   // lines.
  7416.   TimelineView.HIDDEN_CLASS = 'timeline-view-hidden';
  7417.  
  7418.   cr.addSingletonGetter(TimelineView);
  7419.  
  7420.   // Frequency with which we increase update the end date to be the current
  7421.   // time, when actively capturing events.
  7422.   var UPDATE_INTERVAL_MS = 2000;
  7423.  
  7424.   TimelineView.prototype = {
  7425.     // Inherit the superclass's methods.
  7426.     __proto__: superClass.prototype,
  7427.  
  7428.     setGeometry: function(left, top, width, height) {
  7429.       superClass.prototype.setGeometry.call(this, left, top, width, height);
  7430.     },
  7431.  
  7432.     show: function(isVisible) {
  7433.       superClass.prototype.show.call(this, isVisible);
  7434.       // If we're hidden or not capturing events, we don't want to update the
  7435.       // graph's range.
  7436.       if (!isVisible || g_browser.isDisabled()) {
  7437.         this.setUpdateEndDateInterval_(0);
  7438.         return;
  7439.       }
  7440.  
  7441.       // Otherwise, update the visible range on a timer.
  7442.       this.setUpdateEndDateInterval_(UPDATE_INTERVAL_MS);
  7443.       this.updateEndDate_();
  7444.     },
  7445.  
  7446.     /**
  7447.      * Starts calling the GraphView's updateEndDate function every |intervalMs|
  7448.      * milliseconds.  If |intervalMs| is 0, stops calling the function.
  7449.      */
  7450.     setUpdateEndDateInterval_: function(intervalMs) {
  7451.       if (this.updateIntervalId_ !== null) {
  7452.         window.clearInterval(this.updateIntervalId_);
  7453.         this.updateIntervalId_ = null;
  7454.       }
  7455.       if (intervalMs > 0) {
  7456.         this.updateIntervalId_ =
  7457.             window.setInterval(this.updateEndDate_.bind(this), intervalMs);
  7458.       }
  7459.     },
  7460.  
  7461.     /**
  7462.      * Updates the end date of graph to be the current time, unless the
  7463.      * BrowserBridge is disabled.
  7464.      */
  7465.     updateEndDate_: function() {
  7466.       // If we loaded a log file or capturing data was stopped, stop the timer.
  7467.       if (g_browser.isDisabled()) {
  7468.         this.setUpdateEndDateInterval_(0);
  7469.         return;
  7470.       }
  7471.       this.graphView_.updateEndDate();
  7472.     },
  7473.  
  7474.     onLoadLogFinish: function(data) {
  7475.       this.setUpdateEndDateInterval_(0);
  7476.       return true;
  7477.     },
  7478.  
  7479.     /**
  7480.      * Updates the visibility state of |dataSeries| to correspond to the
  7481.      * current checked state of |checkBox|.  Also updates the class of
  7482.      * |listItem| based on the new visibility state.
  7483.      */
  7484.     updateDataSeriesVisibility_: function(dataSeries, listItem, checkBox) {
  7485.       dataSeries.show(checkBox.checked);
  7486.       if (checkBox.checked)
  7487.         listItem.classList.remove(TimelineView.HIDDEN_CLASS);
  7488.       else
  7489.         listItem.classList.add(TimelineView.HIDDEN_CLASS);
  7490.     },
  7491.  
  7492.     dataSeriesClicked_: function(dataSeries, listItem, checkBox) {
  7493.       this.updateDataSeriesVisibility_(dataSeries, listItem, checkBox);
  7494.       this.graphView_.repaint();
  7495.     },
  7496.  
  7497.     /**
  7498.      * Adds the specified DataSeries to |dataSeries_|, and hooks up
  7499.      * |listItemId|'s checkbox and color to correspond to the current state
  7500.      * of the given DataSeries.
  7501.      */
  7502.     addDataSeries_: function(dataSeries, listItemId) {
  7503.       this.dataSeries_.push(dataSeries);
  7504.       var listItem = $(listItemId);
  7505.       var checkBox = $(listItemId).querySelector('input');
  7506.  
  7507.       // Make sure |listItem| is visible, and then use its color for the
  7508.       // DataSource.
  7509.       listItem.classList.remove(TimelineView.HIDDEN_CLASS);
  7510.       dataSeries.setColor(getComputedStyle(listItem).color);
  7511.  
  7512.       this.updateDataSeriesVisibility_(dataSeries, listItem, checkBox);
  7513.       checkBox.onclick = this.dataSeriesClicked_.bind(this, dataSeries,
  7514.                                                       listItem, checkBox);
  7515.     },
  7516.  
  7517.     /**
  7518.      * Recreate all DataSeries.  Global constants must have been set before
  7519.      * this is called.
  7520.      */
  7521.     createDataSeries_: function() {
  7522.       this.graphRangeInitialized_ = false;
  7523.       this.dataSeries_ = [];
  7524.  
  7525.       this.addDataSeries_(new SourceCountDataSeries(
  7526.                               EventSourceType.SOCKET,
  7527.                               EventType.SOCKET_ALIVE),
  7528.                           TimelineView.OPEN_SOCKETS_ID);
  7529.  
  7530.       this.addDataSeries_(new SocketsInUseDataSeries(),
  7531.                           TimelineView.IN_USE_SOCKETS_ID);
  7532.  
  7533.       this.addDataSeries_(new SourceCountDataSeries(
  7534.                               EventSourceType.URL_REQUEST,
  7535.                               EventType.REQUEST_ALIVE),
  7536.                           TimelineView.URL_REQUESTS_ID);
  7537.  
  7538.       this.addDataSeries_(new SourceCountDataSeries(
  7539.                               EventSourceType.HOST_RESOLVER_IMPL_REQUEST,
  7540.                               EventType.HOST_RESOLVER_IMPL_REQUEST),
  7541.                           TimelineView.DNS_REQUESTS_ID);
  7542.  
  7543.       this.addDataSeries_(new SourceCountDataSeries(
  7544.                               EventSourceType.HOST_RESOLVER_IMPL_JOB,
  7545.                               EventType.HOST_RESOLVER_IMPL_JOB),
  7546.                           TimelineView.DNS_JOBS_ID);
  7547.  
  7548.       this.addDataSeries_(new NetworkTransferRateDataSeries(
  7549.                               EventType.SOCKET_BYTES_RECEIVED,
  7550.                               EventType.UDP_BYTES_RECEIVED),
  7551.                           TimelineView.BYTES_RECEIVED_ID);
  7552.  
  7553.       this.addDataSeries_(new NetworkTransferRateDataSeries(
  7554.                               EventType.SOCKET_BYTES_SENT,
  7555.                               EventType.UDP_BYTES_SENT),
  7556.                           TimelineView.BYTES_SENT_ID);
  7557.  
  7558.       this.addDataSeries_(new DiskCacheTransferRateDataSeries(
  7559.                               EventType.ENTRY_READ_DATA),
  7560.                           TimelineView.DISK_CACHE_BYTES_READ_ID);
  7561.  
  7562.       this.addDataSeries_(new DiskCacheTransferRateDataSeries(
  7563.                               EventType.ENTRY_WRITE_DATA),
  7564.                           TimelineView.DISK_CACHE_BYTES_WRITTEN_ID);
  7565.  
  7566.       this.graphView_.setDataSeries(this.dataSeries_);
  7567.     },
  7568.  
  7569.     /**
  7570.      * When we receive the constants, create or recreate the DataSeries.
  7571.      */
  7572.     onReceivedConstants: function(constants) {
  7573.       this.createDataSeries_();
  7574.     },
  7575.  
  7576.     /**
  7577.      * When all log entries are deleted, recreate the DataSeries.
  7578.      */
  7579.     onAllLogEntriesDeleted: function() {
  7580.       this.graphRangeInitialized_ = false;
  7581.       this.createDataSeries_();
  7582.     },
  7583.  
  7584.     onReceivedLogEntries: function(entries) {
  7585.       // Pass each entry to every DataSeries, one at a time.  Not having each
  7586.       // data series get data directly from the EventsTracker saves us from
  7587.       // having very un-Javascript-like destructors for when we load new,
  7588.       // constants and slightly simplifies DataSeries objects.
  7589.       for (var entry = 0; entry < entries.length; ++entry) {
  7590.         for (var i = 0; i < this.dataSeries_.length; ++i)
  7591.           this.dataSeries_[i].onReceivedLogEntry(entries[entry]);
  7592.       }
  7593.  
  7594.       // If this is the first non-empty set of entries we've received, or we're
  7595.       // viewing a loaded log file, we will need to update the date range.
  7596.       if (this.graphRangeInitialized_ && !MainView.isViewingLoadedLog())
  7597.         return;
  7598.       if (entries.length == 0)
  7599.         return;
  7600.  
  7601.       // Update the date range.
  7602.       var startDate;
  7603.       if (!this.graphRangeInitialized_) {
  7604.         startDate = timeutil.convertTimeTicksToDate(entries[0].time);
  7605.       } else {
  7606.         startDate = this.graphView_.getStartDate();
  7607.       }
  7608.       var endDate =
  7609.           timeutil.convertTimeTicksToDate(entries[entries.length - 1].time);
  7610.       this.graphView_.setDateRange(startDate, endDate);
  7611.       this.graphRangeInitialized_ = true;
  7612.     },
  7613.  
  7614.     toggleSelectionDiv_: function() {
  7615.       var toggle = $(TimelineView.SELECTION_TOGGLE_ID);
  7616.       var shouldCollapse = toggle.className == 'timeline-view-rotateleft';
  7617.  
  7618.       setNodeDisplay($(TimelineView.SELECTION_UL_ID), !shouldCollapse);
  7619.       toggle.className = shouldCollapse ?
  7620.           'timeline-view-rotateright' : 'timeline-view-rotateleft';
  7621.  
  7622.       // Figure out the appropriate width for the selection div.
  7623.       var newWidth;
  7624.       if (shouldCollapse) {
  7625.         newWidth = toggle.offsetWidth;
  7626.       } else {
  7627.         newWidth = this.selectionDivFullWidth_;
  7628.       }
  7629.  
  7630.       // Change the width on the selection view (doesn't matter what we
  7631.       // set the other values to, since we will re-layout in the next line).
  7632.       this.leftView_.setGeometry(0, 0, newWidth, 100);
  7633.  
  7634.       // Force a re-layout now that the left view has changed width.
  7635.       this.setGeometry(this.getLeft(), this.getTop(), this.getWidth(),
  7636.                        this.getHeight());
  7637.     }
  7638.   };
  7639.  
  7640.   return TimelineView;
  7641. })();
  7642.  
  7643. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  7644. // Use of this source code is governed by a BSD-style license that can be
  7645. // found in the LICENSE file.
  7646.  
  7647. // TODO(eroman): put these methods into a namespace.
  7648.  
  7649. var printLogEntriesAsText;
  7650. var searchLogEntriesForText;
  7651. var proxySettingsToString;
  7652. var stripCookiesAndLoginInfo;
  7653.  
  7654. // Start of anonymous namespace.
  7655. (function() {
  7656. 'use strict';
  7657.  
  7658. function canCollapseBeginWithEnd(beginEntry) {
  7659.   return beginEntry &&
  7660.          beginEntry.isBegin() &&
  7661.          beginEntry.end &&
  7662.          beginEntry.end.index == beginEntry.index + 1 &&
  7663.          (!beginEntry.orig.params || !beginEntry.end.orig.params);
  7664. }
  7665.  
  7666. /**
  7667.  * Adds a child pre element to the end of |parent|, and writes the
  7668.  * formatted contents of |logEntries| to it.
  7669.  */
  7670. printLogEntriesAsText = function(logEntries, parent, privacyStripping,
  7671.                                  logCreationTime) {
  7672.   var tablePrinter = createTablePrinter(logEntries, privacyStripping,
  7673.                                         logCreationTime);
  7674.  
  7675.   // Format the table for fixed-width text.
  7676.   tablePrinter.toText(0, parent);
  7677. }
  7678.  
  7679. /**
  7680.  * Searches the table that would be output by printLogEntriesAsText for
  7681.  * |searchString|.  Returns true if |searchString| would appear entirely within
  7682.  * any field in the table.  |searchString| must be lowercase.
  7683.  *
  7684.  * Seperate function from printLogEntriesAsText since TablePrinter.toText
  7685.  * modifies the DOM.
  7686.  */
  7687. searchLogEntriesForText = function(searchString, logEntries, privacyStripping) {
  7688.   var tablePrinter =
  7689.       createTablePrinter(logEntries, privacyStripping, undefined);
  7690.  
  7691.   // Format the table for fixed-width text.
  7692.   return tablePrinter.search(searchString);
  7693. }
  7694.  
  7695. /**
  7696.  * Creates a TablePrinter for use by the above two functions.
  7697.  */
  7698. function createTablePrinter(logEntries, privacyStripping, logCreationTime) {
  7699.   var entries = LogGroupEntry.createArrayFrom(logEntries);
  7700.   var tablePrinter = new TablePrinter();
  7701.   var parameterOutputter = new ParameterOutputter(tablePrinter);
  7702.  
  7703.   if (entries.length == 0)
  7704.     return tablePrinter;
  7705.  
  7706.   var startTime = timeutil.convertTimeTicksToTime(entries[0].orig.time);
  7707.  
  7708.   for (var i = 0; i < entries.length; ++i) {
  7709.     var entry = entries[i];
  7710.  
  7711.     // Avoid printing the END for a BEGIN that was immediately before, unless
  7712.     // both have extra parameters.
  7713.     if (!entry.isEnd() || !canCollapseBeginWithEnd(entry.begin)) {
  7714.       var entryTime = timeutil.convertTimeTicksToTime(entry.orig.time);
  7715.       addRowWithTime(tablePrinter, entryTime, startTime);
  7716.  
  7717.       for (var j = entry.getDepth(); j > 0; --j)
  7718.         tablePrinter.addCell('  ');
  7719.  
  7720.       var eventText = getTextForEvent(entry);
  7721.       // Get the elapsed time, and append it to the event text.
  7722.       if (entry.isBegin()) {
  7723.         var dt = '?';
  7724.         // Definite time.
  7725.         if (entry.end) {
  7726.           dt = entry.end.orig.time - entry.orig.time;
  7727.         } else if (logCreationTime != undefined) {
  7728.           dt = (logCreationTime - entryTime) + '+';
  7729.         }
  7730.         eventText += '  [dt=' + dt + ']';
  7731.       }
  7732.  
  7733.       var mainCell = tablePrinter.addCell(eventText);
  7734.       mainCell.allowOverflow = true;
  7735.     }
  7736.  
  7737.     // Output the extra parameters.
  7738.     if (typeof entry.orig.params == 'object') {
  7739.       // Those 5 skipped cells are: two for "t=", and three for "st=".
  7740.       tablePrinter.setNewRowCellIndent(5 + entry.getDepth());
  7741.       writeParameters(entry.orig, privacyStripping, parameterOutputter);
  7742.  
  7743.       tablePrinter.setNewRowCellIndent(0);
  7744.     }
  7745.   }
  7746.  
  7747.   // If viewing a saved log file, add row with just the time the log was
  7748.   // created, if the event never completed.
  7749.   if (logCreationTime != undefined &&
  7750.       entries[entries.length - 1].getDepth() > 0) {
  7751.     addRowWithTime(tablePrinter, logCreationTime, startTime);
  7752.   }
  7753.  
  7754.   return tablePrinter;
  7755. }
  7756.  
  7757. /**
  7758.  * Adds a new row to the given TablePrinter, and adds five cells containing
  7759.  * information about the time an event occured.
  7760.  * Format is '[t=<UTC time in ms>] [st=<ms since the source started>]'.
  7761.  * @param {TablePrinter} tablePrinter The table printer to add the cells to.
  7762.  * @param {number} eventTime The time the event occured, as a UTC time in
  7763.  *     milliseconds.
  7764.  * @param {number} startTime The time the first event for the source occured,
  7765.  *     as a UTC time in milliseconds.
  7766.  */
  7767. function addRowWithTime(tablePrinter, eventTime, startTime) {
  7768.   tablePrinter.addRow();
  7769.   tablePrinter.addCell('t=');
  7770.   var tCell = tablePrinter.addCell(eventTime);
  7771.   tCell.alignRight = true;
  7772.   tablePrinter.addCell(' [st=');
  7773.   var stCell = tablePrinter.addCell(eventTime - startTime);
  7774.   stCell.alignRight = true;
  7775.   tablePrinter.addCell('] ');
  7776. }
  7777.  
  7778. /**
  7779.  * |hexString| must be a string of hexadecimal characters with no whitespace,
  7780.  * whose length is a multiple of two.  Writes multiple lines to |out| with
  7781.  * the hexadecimal characters from |hexString| on the left, in groups of
  7782.  * two, and their corresponding ASCII characters on the right.
  7783.  *
  7784.  * |asciiCharsPerLine| specifies how many ASCII characters will be put on each
  7785.  * line of the output string.
  7786.  */
  7787. function writeHexString(hexString, asciiCharsPerLine, out) {
  7788.   // Number of transferred bytes in a line of output.  Length of a
  7789.   // line is roughly 4 times larger.
  7790.   var hexCharsPerLine = 2 * asciiCharsPerLine;
  7791.   for (var i = 0; i < hexString.length; i += hexCharsPerLine) {
  7792.     var hexLine = '';
  7793.     var asciiLine = '';
  7794.     for (var j = i; j < i + hexCharsPerLine && j < hexString.length; j += 2) {
  7795.       var hex = hexString.substr(j, 2);
  7796.       hexLine += hex + ' ';
  7797.       var charCode = parseInt(hex, 16);
  7798.       // For ASCII codes 32 though 126, display the corresponding
  7799.       // characters.  Use a space for nulls, and a period for
  7800.       // everything else.
  7801.       if (charCode >= 0x20 && charCode <= 0x7E) {
  7802.         asciiLine += String.fromCharCode(charCode);
  7803.       } else if (charCode == 0x00) {
  7804.         asciiLine += ' ';
  7805.       } else {
  7806.         asciiLine += '.';
  7807.       }
  7808.     }
  7809.  
  7810.     // Make the ASCII text for the last line of output align with the previous
  7811.     // lines.
  7812.     hexLine += makeRepeatedString(' ', 3 * asciiCharsPerLine - hexLine.length);
  7813.     out.writeLine('   ' + hexLine + '  ' + asciiLine);
  7814.   }
  7815. }
  7816.  
  7817. /**
  7818.  * Wrapper around a TablePrinter to simplify outputting lines of text for event
  7819.  * parameters.
  7820.  */
  7821. var ParameterOutputter = (function() {
  7822.   /**
  7823.    * @constructor
  7824.    */
  7825.   function ParameterOutputter(tablePrinter) {
  7826.     this.tablePrinter_ = tablePrinter;
  7827.   }
  7828.  
  7829.   ParameterOutputter.prototype = {
  7830.     /**
  7831.      * Outputs a single line.
  7832.      */
  7833.     writeLine: function(line) {
  7834.       this.tablePrinter_.addRow();
  7835.       var cell = this.tablePrinter_.addCell(line);
  7836.       cell.allowOverflow = true;
  7837.       return cell;
  7838.     },
  7839.  
  7840.     /**
  7841.      * Outputs a key=value line which looks like:
  7842.      *
  7843.      *   --> key = value
  7844.      */
  7845.     writeArrowKeyValue: function(key, value, link) {
  7846.       var cell = this.writeLine(kArrow + key + ' = ' + value);
  7847.       cell.link = link;
  7848.     },
  7849.  
  7850.     /**
  7851.      * Outputs a key= line which looks like:
  7852.      *
  7853.      *   --> key =
  7854.      */
  7855.     writeArrowKey: function(key) {
  7856.       this.writeLine(kArrow + key + ' =');
  7857.     },
  7858.  
  7859.     /**
  7860.      * Outputs multiple lines, each indented by numSpaces.
  7861.      * For instance if numSpaces=8 it might look like this:
  7862.      *
  7863.      *         line 1
  7864.      *         line 2
  7865.      *         line 3
  7866.      */
  7867.     writeSpaceIndentedLines: function(numSpaces, lines) {
  7868.       var prefix = makeRepeatedString(' ', numSpaces);
  7869.       for (var i = 0; i < lines.length; ++i)
  7870.         this.writeLine(prefix + lines[i]);
  7871.     },
  7872.  
  7873.     /**
  7874.      * Outputs multiple lines such that the first line has
  7875.      * an arrow pointing at it, and subsequent lines
  7876.      * align with the first one. For example:
  7877.      *
  7878.      *   --> line 1
  7879.      *       line 2
  7880.      *       line 3
  7881.      */
  7882.     writeArrowIndentedLines: function(lines) {
  7883.       if (lines.length == 0)
  7884.         return;
  7885.  
  7886.       this.writeLine(kArrow + lines[0]);
  7887.  
  7888.       for (var i = 1; i < lines.length; ++i)
  7889.         this.writeLine(kArrowIndentation + lines[i]);
  7890.     }
  7891.   };
  7892.  
  7893.   var kArrow = ' --> ';
  7894.   var kArrowIndentation = '     ';
  7895.  
  7896.   return ParameterOutputter;
  7897. })();  // end of ParameterOutputter
  7898.  
  7899. /**
  7900.  * Formats the parameters for |entry| and writes them to |out|.
  7901.  * Certain event types have custom pretty printers. Everything else will
  7902.  * default to a JSON-like format.
  7903.  */
  7904. function writeParameters(entry, privacyStripping, out) {
  7905.   if (privacyStripping) {
  7906.     // If privacy stripping is enabled, remove data as needed.
  7907.     entry = stripCookiesAndLoginInfo(entry);
  7908.   } else {
  7909.     // If headers are in an object, convert them to an array for better display.
  7910.     entry = reformatHeaders(entry);
  7911.   }
  7912.  
  7913.   // Use any parameter writer available for this event type.
  7914.   var paramsWriter = getParamaterWriterForEventType(entry.type);
  7915.   var consumedParams = {};
  7916.   if (paramsWriter)
  7917.     paramsWriter(entry, out, consumedParams);
  7918.  
  7919.   // Write any un-consumed parameters.
  7920.   for (var k in entry.params) {
  7921.     if (consumedParams[k])
  7922.       continue;
  7923.     defaultWriteParameter(k, entry.params[k], out);
  7924.   }
  7925. }
  7926.  
  7927. /**
  7928.  * Finds a writer to format the parameters for events of type |eventType|.
  7929.  *
  7930.  * @return {function} The returned function "writer" can be invoked
  7931.  *                    as |writer(entry, writer, consumedParams)|. It will
  7932.  *                    output the parameters of |entry| to |out|, and fill
  7933.  *                    |consumedParams| with the keys of the parameters
  7934.  *                    consumed. If no writer is available for |eventType| then
  7935.  *                    returns null.
  7936.  */
  7937. function getParamaterWriterForEventType(eventType) {
  7938.   switch (eventType) {
  7939.     case EventType.HTTP_TRANSACTION_SEND_REQUEST_HEADERS:
  7940.     case EventType.HTTP_TRANSACTION_SEND_TUNNEL_HEADERS:
  7941.       return writeParamsForRequestHeaders;
  7942.  
  7943.     case EventType.PROXY_CONFIG_CHANGED:
  7944.       return writeParamsForProxyConfigChanged;
  7945.  
  7946.     case EventType.CERT_VERIFIER_JOB:
  7947.     case EventType.SSL_CERTIFICATES_RECEIVED:
  7948.       return writeParamsForCertificates;
  7949.  
  7950.     case EventType.SSL_VERSION_FALLBACK:
  7951.       return writeParamsForSSLVersionFallback;
  7952.   }
  7953.   return null;
  7954. }
  7955.  
  7956. /**
  7957.  * Default parameter writer that outputs a visualization of field named |key|
  7958.  * with value |value| to |out|.
  7959.  */
  7960. function defaultWriteParameter(key, value, out) {
  7961.   if (key == 'headers' && value instanceof Array) {
  7962.     out.writeArrowIndentedLines(value);
  7963.     return;
  7964.   }
  7965.  
  7966.   // For transferred bytes, display the bytes in hex and ASCII.
  7967.   if (key == 'hex_encoded_bytes' && typeof value == 'string') {
  7968.     out.writeArrowKey(key);
  7969.     writeHexString(value, 20, out);
  7970.     return;
  7971.   }
  7972.  
  7973.   // Handle source_dependency entries - add link and map source type to
  7974.   // string.
  7975.   if (key == 'source_dependency' && typeof value == 'object') {
  7976.     var link = '#events&s=' + value.id;
  7977.     var valueStr = value.id + ' (' + EventSourceTypeNames[value.type] + ')';
  7978.     out.writeArrowKeyValue(key, valueStr, link);
  7979.     return;
  7980.   }
  7981.  
  7982.   if (key == 'net_error' && typeof value == 'number') {
  7983.     var valueStr = value + ' (' + netErrorToString(value) + ')';
  7984.     out.writeArrowKeyValue(key, valueStr);
  7985.     return;
  7986.   }
  7987.  
  7988.   if (key == 'load_flags' && typeof value == 'number') {
  7989.     var valueStr = value + ' (' + getLoadFlagSymbolicString(value) + ')';
  7990.     out.writeArrowKeyValue(key, valueStr);
  7991.     return;
  7992.   }
  7993.  
  7994.   if (key == 'load_state' && typeof value == 'number') {
  7995.     var valueStr = value + ' (' + getKeyWithValue(LoadState, value) + ')';
  7996.     out.writeArrowKeyValue(key, valueStr);
  7997.     return;
  7998.   }
  7999.  
  8000.   // Otherwise just default to JSON formatting of the value.
  8001.   out.writeArrowKeyValue(key, JSON.stringify(value));
  8002. }
  8003.  
  8004. /**
  8005.  * Returns the set of LoadFlags that make up the integer |loadFlag|.
  8006.  * For example: getLoadFlagSymbolicString(
  8007.  */
  8008. function getLoadFlagSymbolicString(loadFlag) {
  8009.   // Load flag of 0 means "NORMAL". Special case this, since and-ing with
  8010.   // 0 is always going to be false.
  8011.   if (loadFlag == 0)
  8012.     return getKeyWithValue(LoadFlag, loadFlag);
  8013.  
  8014.   var matchingLoadFlagNames = [];
  8015.  
  8016.   for (var k in LoadFlag) {
  8017.     if (loadFlag & LoadFlag[k])
  8018.       matchingLoadFlagNames.push(k);
  8019.   }
  8020.  
  8021.   return matchingLoadFlagNames.join(' | ');
  8022. }
  8023.  
  8024. /**
  8025.  * Converts an SSL version number to a textual representation.
  8026.  * For instance, SSLVersionNumberToName(0x0301) returns 'TLS 1.0'.
  8027.  */
  8028. function SSLVersionNumberToName(version) {
  8029.   if ((version & 0xFFFF) != version) {
  8030.     // If the version number is more than 2 bytes long something is wrong.
  8031.     // Print it as hex.
  8032.     return 'SSL 0x' + version.toString(16);
  8033.   }
  8034.  
  8035.   // See if it is a known TLS name.
  8036.   var kTLSNames = {
  8037.     0x0301: 'TLS 1.0',
  8038.     0x0302: 'TLS 1.1',
  8039.     0x0303: 'TLS 1.2'
  8040.   };
  8041.   var name = kTLSNames[version];
  8042.   if (name)
  8043.     return name;
  8044.  
  8045.   // Otherwise label it as an SSL version.
  8046.   var major = (version & 0xFF00) >> 8;
  8047.   var minor = version & 0x00FF;
  8048.  
  8049.   return 'SSL ' + major + '.' + minor;
  8050. }
  8051.  
  8052. /**
  8053.  * TODO(eroman): get rid of this, as it is only used by 1 callsite.
  8054.  *
  8055.  * Indent |lines| by |start|.
  8056.  *
  8057.  * For example, if |start| = ' -> ' and |lines| = ['line1', 'line2', 'line3']
  8058.  * the output will be:
  8059.  *
  8060.  *   " -> line1\n" +
  8061.  *   "    line2\n" +
  8062.  *   "    line3"
  8063.  */
  8064. function indentLines(start, lines) {
  8065.   return start + lines.join('\n' + makeRepeatedString(' ', start.length));
  8066. }
  8067.  
  8068. /**
  8069.  * If entry.param.headers exists and is an object other than an array, converts
  8070.  * it into an array and returns a new entry.  Otherwise, just returns the
  8071.  * original entry.
  8072.  */
  8073. function reformatHeaders(entry) {
  8074.   // If there are no headers, or it is not an object other than an array,
  8075.   // return |entry| without modification.
  8076.   if (!entry.params || entry.params.headers === undefined ||
  8077.       typeof entry.params.headers != 'object' ||
  8078.       entry.params.headers instanceof Array) {
  8079.     return entry;
  8080.   }
  8081.  
  8082.   // Duplicate the top level object, and |entry.params|, so the original object
  8083.   // will not be modified.
  8084.   entry = shallowCloneObject(entry);
  8085.   entry.params = shallowCloneObject(entry.params);
  8086.  
  8087.   // Convert headers to an array.
  8088.   var headers = [];
  8089.   for (var key in entry.params.headers)
  8090.     headers.push(key + ': ' + entry.params.headers[key]);
  8091.   entry.params.headers = headers;
  8092.  
  8093.   return entry;
  8094. }
  8095.  
  8096. /**
  8097.  * Removes a cookie or unencrypted login information from a single HTTP header
  8098.  * line, if present, and returns the modified line.  Otherwise, just returns
  8099.  * the original line.
  8100.  */
  8101. function stripCookieOrLoginInfo(line) {
  8102.   var patterns = [
  8103.       // Cookie patterns
  8104.       /^set-cookie: /i,
  8105.       /^set-cookie2: /i,
  8106.       /^cookie: /i,
  8107.  
  8108.       // Unencrypted authentication patterns
  8109.       /^authorization: \S*\s*/i,
  8110.       /^proxy-authorization: \S*\s*/i];
  8111.  
  8112.   // Prefix will hold the first part of the string that contains no private
  8113.   // information.  If null, no part of the string contains private information.
  8114.   var prefix = null;
  8115.   for (var i = 0; i < patterns.length; i++) {
  8116.     var match = patterns[i].exec(line);
  8117.     if (match != null) {
  8118.       prefix = match[0];
  8119.       break;
  8120.     }
  8121.   }
  8122.  
  8123.   // Look for authentication information from data received from the server in
  8124.   // multi-round Negotiate authentication.
  8125.   if (prefix === null) {
  8126.     var challengePatterns = [
  8127.         /^www-authenticate: (\S*)\s*/i,
  8128.         /^proxy-authenticate: (\S*)\s*/i];
  8129.     for (var i = 0; i < challengePatterns.length; i++) {
  8130.       var match = challengePatterns[i].exec(line);
  8131.       if (!match)
  8132.         continue;
  8133.  
  8134.       // If there's no data after the scheme name, do nothing.
  8135.       if (match[0].length == line.length)
  8136.         break;
  8137.  
  8138.       // Ignore lines with commas, as they may contain lists of schemes, and
  8139.       // the information we want to hide is Base64 encoded, so has no commas.
  8140.       if (line.indexOf(',') >= 0)
  8141.         break;
  8142.  
  8143.       // Ignore Basic and Digest authentication challenges, as they contain
  8144.       // public information.
  8145.       if (/^basic$/i.test(match[1]) || /^digest$/i.test(match[1]))
  8146.         break;
  8147.  
  8148.       prefix = match[0];
  8149.       break;
  8150.     }
  8151.   }
  8152.  
  8153.   if (prefix) {
  8154.     var suffix = line.slice(prefix.length);
  8155.     // If private information has already been removed, keep the line as-is.
  8156.     // This is often the case when viewing a loaded log.
  8157.     // TODO(mmenke):  Remove '[value was stripped]' check once M24 hits stable.
  8158.     if (suffix.search(/^\[[0-9]+ bytes were stripped\]$/) == -1 &&
  8159.         suffix != '[value was stripped]') {
  8160.       return prefix + '[' + suffix.length + ' bytes were stripped]';
  8161.     }
  8162.   }
  8163.  
  8164.   return line;
  8165. }
  8166.  
  8167. /**
  8168.  * If |entry| has headers, returns a copy of |entry| with all cookie and
  8169.  * unencrypted login text removed.  Otherwise, returns original |entry| object.
  8170.  * This is needed so that JSON log dumps can be made without affecting the
  8171.  * source data.  Converts headers stored in objects to arrays.
  8172.  */
  8173. stripCookiesAndLoginInfo = function(entry) {
  8174.   if (!entry.params || entry.params.headers === undefined ||
  8175.       !(entry.params.headers instanceof Object)) {
  8176.     return entry;
  8177.   }
  8178.  
  8179.   // Make sure entry's headers are in an array.
  8180.   entry = reformatHeaders(entry);
  8181.  
  8182.   // Duplicate the top level object, and |entry.params|.  All other fields are
  8183.   // just pointers to the original values, as they won't be modified, other than
  8184.   // |entry.params.headers|.
  8185.   entry = shallowCloneObject(entry);
  8186.   entry.params = shallowCloneObject(entry.params);
  8187.  
  8188.   entry.params.headers = entry.params.headers.map(stripCookieOrLoginInfo);
  8189.   return entry;
  8190. }
  8191.  
  8192. /**
  8193.  * Outputs the request header parameters of |entry| to |out|.
  8194.  */
  8195. function writeParamsForRequestHeaders(entry, out, consumedParams) {
  8196.   var params = entry.params;
  8197.  
  8198.   if (!(typeof params.line == 'string') || !(params.headers instanceof Array)) {
  8199.     // Unrecognized params.
  8200.     return;
  8201.   }
  8202.  
  8203.   // Strip the trailing CRLF that params.line contains.
  8204.   var lineWithoutCRLF = params.line.replace(/\r\n$/g, '');
  8205.   out.writeArrowIndentedLines([lineWithoutCRLF].concat(params.headers));
  8206.  
  8207.   consumedParams.line = true;
  8208.   consumedParams.headers = true;
  8209. }
  8210.  
  8211. /**
  8212.  * Outputs the certificate parameters of |entry| to |out|.
  8213.  */
  8214. function writeParamsForCertificates(entry, out, consumedParams) {
  8215.   if (!(entry.params.certificates instanceof Array)) {
  8216.     // Unrecognized params.
  8217.     return;
  8218.   }
  8219.  
  8220.   var certs = entry.params.certificates.reduce(function(previous, current) {
  8221.     return previous.concat(current.split('\n'));
  8222.   }, new Array());
  8223.  
  8224.   out.writeArrowKey('certificates');
  8225.   out.writeSpaceIndentedLines(8, certs);
  8226.  
  8227.   consumedParams.certificates = true;
  8228. }
  8229.  
  8230. /**
  8231.  * Outputs the SSL version fallback parameters of |entry| to |out|.
  8232.  */
  8233. function writeParamsForSSLVersionFallback(entry, out, consumedParams) {
  8234.   var params = entry.params;
  8235.  
  8236.   if (typeof params.version_before != 'number' ||
  8237.       typeof params.version_after != 'number') {
  8238.     // Unrecognized params.
  8239.     return;
  8240.   }
  8241.  
  8242.   var line = SSLVersionNumberToName(params.version_before) +
  8243.              ' ==> ' +
  8244.              SSLVersionNumberToName(params.version_after);
  8245.   out.writeArrowIndentedLines([line]);
  8246.  
  8247.   consumedParams.version_before = true;
  8248.   consumedParams.version_after = true;
  8249. }
  8250.  
  8251. function writeParamsForProxyConfigChanged(entry, out, consumedParams) {
  8252.   var params = entry.params;
  8253.  
  8254.   if (typeof params.new_config != 'object') {
  8255.     // Unrecognized params.
  8256.     return;
  8257.   }
  8258.  
  8259.   if (typeof params.old_config == 'object') {
  8260.     var oldConfigString = proxySettingsToString(params.old_config);
  8261.     // The previous configuration may not be present in the case of
  8262.     // the initial proxy settings fetch.
  8263.     out.writeArrowKey('old_config');
  8264.  
  8265.     out.writeSpaceIndentedLines(8, oldConfigString.split('\n'));
  8266.  
  8267.     consumedParams.old_config = true;
  8268.   }
  8269.  
  8270.   var newConfigString = proxySettingsToString(params.new_config);
  8271.   out.writeArrowKey('new_config');
  8272.   out.writeSpaceIndentedLines(8, newConfigString.split('\n'));
  8273.  
  8274.   consumedParams.new_config = true;
  8275. }
  8276.  
  8277. function getTextForEvent(entry) {
  8278.   var text = '';
  8279.  
  8280.   if (entry.isBegin() && canCollapseBeginWithEnd(entry)) {
  8281.     // Don't prefix with '+' if we are going to collapse the END event.
  8282.     text = ' ';
  8283.   } else if (entry.isBegin()) {
  8284.     text = '+' + text;
  8285.   } else if (entry.isEnd()) {
  8286.     text = '-' + text;
  8287.   } else {
  8288.     text = ' ';
  8289.   }
  8290.  
  8291.   text += EventTypeNames[entry.orig.type];
  8292.   return text;
  8293. }
  8294.  
  8295. proxySettingsToString = function(config) {
  8296.   if (!config)
  8297.     return '';
  8298.  
  8299.   // The proxy settings specify up to three major fallback choices
  8300.   // (auto-detect, custom pac url, or manual settings).
  8301.   // We enumerate these to a list so we can later number them.
  8302.   var modes = [];
  8303.  
  8304.   // Output any automatic settings.
  8305.   if (config.auto_detect)
  8306.     modes.push(['Auto-detect']);
  8307.   if (config.pac_url)
  8308.     modes.push(['PAC script: ' + config.pac_url]);
  8309.  
  8310.   // Output any manual settings.
  8311.   if (config.single_proxy || config.proxy_per_scheme) {
  8312.     var lines = [];
  8313.  
  8314.     if (config.single_proxy) {
  8315.       lines.push('Proxy server: ' + config.single_proxy);
  8316.     } else if (config.proxy_per_scheme) {
  8317.       for (var urlScheme in config.proxy_per_scheme) {
  8318.         if (urlScheme != 'fallback') {
  8319.           lines.push('Proxy server for ' + urlScheme.toUpperCase() + ': ' +
  8320.                      config.proxy_per_scheme[urlScheme]);
  8321.         }
  8322.       }
  8323.       if (config.proxy_per_scheme.fallback) {
  8324.         lines.push('Proxy server for everything else: ' +
  8325.                    config.proxy_per_scheme.fallback);
  8326.       }
  8327.     }
  8328.  
  8329.     // Output any proxy bypass rules.
  8330.     if (config.bypass_list) {
  8331.       if (config.reverse_bypass) {
  8332.         lines.push('Reversed bypass list: ');
  8333.       } else {
  8334.         lines.push('Bypass list: ');
  8335.       }
  8336.  
  8337.       for (var i = 0; i < config.bypass_list.length; ++i)
  8338.         lines.push('  ' + config.bypass_list[i]);
  8339.     }
  8340.  
  8341.     modes.push(lines);
  8342.   }
  8343.  
  8344.   var result = [];
  8345.   if (modes.length < 1) {
  8346.     // If we didn't find any proxy settings modes, we are using DIRECT.
  8347.     result.push('Use DIRECT connections.');
  8348.   } else if (modes.length == 1) {
  8349.     // If there was just one mode, don't bother numbering it.
  8350.     result.push(modes[0].join('\n'));
  8351.   } else {
  8352.     // Otherwise concatenate all of the modes into a numbered list
  8353.     // (which correspond with the fallback order).
  8354.     for (var i = 0; i < modes.length; ++i)
  8355.       result.push(indentLines('(' + (i + 1) + ') ', modes[i]));
  8356.   }
  8357.  
  8358.   if (config.source != undefined && config.source != 'UNKNOWN')
  8359.     result.push('Source: ' + config.source);
  8360.  
  8361.   return result.join('\n');
  8362. };
  8363.  
  8364. // End of anonymous namespace.
  8365. })();
  8366.  
  8367. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  8368. // Use of this source code is governed by a BSD-style license that can be
  8369. // found in the LICENSE file.
  8370.  
  8371. /**
  8372.  * @fileoverview
  8373.  * LogGroupEntry is a wrapper around log entries, which makes it easier to
  8374.  * find the corresponding start/end of events.
  8375.  *
  8376.  * This is used internally by the log and timeline views to pretty print
  8377.  * collections of log entries.
  8378.  */
  8379.  
  8380. // TODO(eroman): document these methods!
  8381.  
  8382. var LogGroupEntry = (function() {
  8383.   'use strict';
  8384.  
  8385.   function LogGroupEntry(origEntry, index) {
  8386.     this.orig = origEntry;
  8387.     this.index = index;
  8388.   }
  8389.  
  8390.   LogGroupEntry.prototype = {
  8391.     isBegin: function() {
  8392.       return this.orig.phase == EventPhase.PHASE_BEGIN;
  8393.     },
  8394.  
  8395.     isEnd: function() {
  8396.       return this.orig.phase == EventPhase.PHASE_END;
  8397.     },
  8398.  
  8399.     getDepth: function() {
  8400.       var depth = 0;
  8401.       var p = this.parentEntry;
  8402.       while (p) {
  8403.         depth += 1;
  8404.         p = p.parentEntry;
  8405.       }
  8406.       return depth;
  8407.     }
  8408.   };
  8409.  
  8410.   function findParentIndex(parentStack, eventType) {
  8411.     for (var i = parentStack.length - 1; i >= 0; --i) {
  8412.       if (parentStack[i].orig.type == eventType)
  8413.         return i;
  8414.     }
  8415.     return -1;
  8416.   }
  8417.  
  8418.   /**
  8419.    * Returns a list of LogGroupEntrys. This basically wraps the original log
  8420.    * entry, but makes it easier to find the start/end of the event.
  8421.    */
  8422.   LogGroupEntry.createArrayFrom = function(origEntries) {
  8423.     var groupedEntries = [];
  8424.  
  8425.     // Stack of enclosing PHASE_BEGIN elements.
  8426.     var parentStack = [];
  8427.  
  8428.     for (var i = 0; i < origEntries.length; ++i) {
  8429.       var origEntry = origEntries[i];
  8430.  
  8431.       var groupEntry = new LogGroupEntry(origEntry, i);
  8432.       groupedEntries.push(groupEntry);
  8433.  
  8434.       // If this is the end of an event, match it to the start.
  8435.       if (groupEntry.isEnd()) {
  8436.         // Walk up the parent stack to find the corresponding BEGIN for this
  8437.         // END.
  8438.         var parentIndex =
  8439.             findParentIndex(parentStack, groupEntry.orig.type);
  8440.  
  8441.         if (parentIndex == -1) {
  8442.           // Unmatched end.
  8443.         } else {
  8444.           groupEntry.begin = parentStack[parentIndex];
  8445.  
  8446.           // Consider this as the terminator for all open BEGINs up until
  8447.           // parentIndex.
  8448.           while (parentIndex < parentStack.length) {
  8449.             var p = parentStack.pop();
  8450.             p.end = groupEntry;
  8451.           }
  8452.         }
  8453.       }
  8454.  
  8455.       // Inherit the current parent.
  8456.       if (parentStack.length > 0)
  8457.         groupEntry.parentEntry = parentStack[parentStack.length - 1];
  8458.  
  8459.       if (groupEntry.isBegin())
  8460.         parentStack.push(groupEntry);
  8461.     }
  8462.  
  8463.     return groupedEntries;
  8464.   };
  8465.  
  8466.   return LogGroupEntry;
  8467. })();
  8468.  
  8469. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  8470. // Use of this source code is governed by a BSD-style license that can be
  8471. // found in the LICENSE file.
  8472.  
  8473. /**
  8474.  * This view displays information on the proxy setup:
  8475.  *
  8476.  *   - Shows the current proxy settings.
  8477.  *   - Has a button to reload these settings.
  8478.  *   - Shows the list of proxy hostnames that are cached as "bad".
  8479.  *   - Has a button to clear the cached bad proxies.
  8480.  */
  8481. var ProxyView = (function() {
  8482.   'use strict';
  8483.  
  8484.   // We inherit from DivView.
  8485.   var superClass = DivView;
  8486.  
  8487.   /**
  8488.    * @constructor
  8489.    */
  8490.   function ProxyView() {
  8491.     assertFirstConstructorCall(ProxyView);
  8492.  
  8493.     // Call superclass's constructor.
  8494.     superClass.call(this, ProxyView.MAIN_BOX_ID);
  8495.  
  8496.     // Hook up the UI components.
  8497.     $(ProxyView.RELOAD_SETTINGS_BUTTON_ID).onclick =
  8498.         g_browser.sendReloadProxySettings.bind(g_browser);
  8499.     $(ProxyView.CLEAR_BAD_PROXIES_BUTTON_ID).onclick =
  8500.         g_browser.sendClearBadProxies.bind(g_browser);
  8501.  
  8502.     // Register to receive proxy information as it changes.
  8503.     g_browser.addProxySettingsObserver(this, true);
  8504.     g_browser.addBadProxiesObserver(this, true);
  8505.   }
  8506.  
  8507.   // ID for special HTML element in category_tabs.html
  8508.   ProxyView.TAB_HANDLE_ID = 'tab-handle-proxy';
  8509.  
  8510.   // IDs for special HTML elements in proxy_view.html
  8511.   ProxyView.MAIN_BOX_ID = 'proxy-view-tab-content';
  8512.   ProxyView.ORIGINAL_SETTINGS_DIV_ID = 'proxy-view-original-settings';
  8513.   ProxyView.EFFECTIVE_SETTINGS_DIV_ID = 'proxy-view-effective-settings';
  8514.   ProxyView.RELOAD_SETTINGS_BUTTON_ID = 'proxy-view-reload-settings';
  8515.   ProxyView.BAD_PROXIES_TBODY_ID = 'proxy-view-bad-proxies-tbody';
  8516.   ProxyView.CLEAR_BAD_PROXIES_BUTTON_ID = 'proxy-view-clear-bad-proxies';
  8517.  
  8518.   cr.addSingletonGetter(ProxyView);
  8519.  
  8520.   ProxyView.prototype = {
  8521.     // Inherit the superclass's methods.
  8522.     __proto__: superClass.prototype,
  8523.  
  8524.     onLoadLogFinish: function(data) {
  8525.       return this.onProxySettingsChanged(data.proxySettings) &&
  8526.              this.onBadProxiesChanged(data.badProxies);
  8527.     },
  8528.  
  8529.     onProxySettingsChanged: function(proxySettings) {
  8530.       // Both |original| and |effective| are dictionaries describing the
  8531.       // settings.
  8532.       $(ProxyView.ORIGINAL_SETTINGS_DIV_ID).innerHTML = '';
  8533.       $(ProxyView.EFFECTIVE_SETTINGS_DIV_ID).innerHTML = '';
  8534.  
  8535.       if (!proxySettings)
  8536.         return false;
  8537.  
  8538.       var original = proxySettings.original;
  8539.       var effective = proxySettings.effective;
  8540.  
  8541.       $(ProxyView.ORIGINAL_SETTINGS_DIV_ID).innerText =
  8542.           proxySettingsToString(original);
  8543.       $(ProxyView.EFFECTIVE_SETTINGS_DIV_ID).innerText =
  8544.           proxySettingsToString(effective);
  8545.       return true;
  8546.     },
  8547.  
  8548.     onBadProxiesChanged: function(badProxies) {
  8549.       $(ProxyView.BAD_PROXIES_TBODY_ID).innerHTML = '';
  8550.  
  8551.       if (!badProxies)
  8552.         return false;
  8553.  
  8554.       // Add a table row for each bad proxy entry.
  8555.       for (var i = 0; i < badProxies.length; ++i) {
  8556.         var entry = badProxies[i];
  8557.         var badUntilDate = timeutil.convertTimeTicksToDate(entry.bad_until);
  8558.  
  8559.         var tr = addNode($(ProxyView.BAD_PROXIES_TBODY_ID), 'tr');
  8560.  
  8561.         var nameCell = addNode(tr, 'td');
  8562.         var badUntilCell = addNode(tr, 'td');
  8563.  
  8564.         addTextNode(nameCell, entry.proxy_uri);
  8565.         timeutil.addNodeWithDate(badUntilCell, badUntilDate);
  8566.       }
  8567.       return true;
  8568.     }
  8569.   };
  8570.  
  8571.   return ProxyView;
  8572. })();
  8573.  
  8574. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  8575. // Use of this source code is governed by a BSD-style license that can be
  8576. // found in the LICENSE file.
  8577.  
  8578. var SocketPoolWrapper = (function() {
  8579.   'use strict';
  8580.  
  8581.   /**
  8582.    * SocketPoolWrapper is a wrapper around socket pools entries.  It's
  8583.    * used by the log and sockets view to print tables containing both
  8584.    * a synopsis of the state of all pools, and listing the groups within
  8585.    * individual pools.
  8586.    *
  8587.    * The constructor takes a socket pool and its parent, and generates a
  8588.    * unique name from the two, which is stored as |fullName|.  |parent|
  8589.    * must itself be a SocketPoolWrapper.
  8590.    *
  8591.    * @constructor
  8592.    */
  8593.   function SocketPoolWrapper(socketPool, parent) {
  8594.     this.origPool = socketPool;
  8595.     this.fullName = socketPool.name;
  8596.     if (this.fullName != socketPool.type)
  8597.       this.fullName += ' (' + socketPool.type + ')';
  8598.     if (parent)
  8599.       this.fullName = parent.fullName + '->' + this.fullName;
  8600.   }
  8601.  
  8602.   /**
  8603.    * Returns an array of SocketPoolWrappers created from each of the socket
  8604.    * pools in |socketPoolInfo|.  Nested socket pools appear immediately after
  8605.    * their parent, and groups of nodes from trees with root nodes with the same
  8606.    * id are placed adjacent to each other.
  8607.    */
  8608.   SocketPoolWrapper.createArrayFrom = function(socketPoolInfo) {
  8609.     // Create SocketPoolWrappers for each socket pool and separate socket pools
  8610.     // them into different arrays based on root node name.
  8611.     var socketPoolGroups = [];
  8612.     var socketPoolNameLists = {};
  8613.     for (var i = 0; i < socketPoolInfo.length; ++i) {
  8614.       var name = socketPoolInfo[i].name;
  8615.       if (!socketPoolNameLists[name]) {
  8616.         socketPoolNameLists[name] = [];
  8617.         socketPoolGroups.push(socketPoolNameLists[name]);
  8618.       }
  8619.       addSocketPoolsToList(socketPoolNameLists[name], socketPoolInfo[i], null);
  8620.     }
  8621.  
  8622.     // Merge the arrays.
  8623.     var socketPoolList = [];
  8624.     for (var i = 0; i < socketPoolGroups.length; ++i) {
  8625.       socketPoolList = socketPoolList.concat(socketPoolGroups[i]);
  8626.     }
  8627.     return socketPoolList;
  8628.   };
  8629.  
  8630.   /**
  8631.    * Recursively creates SocketPoolWrappers from |origPool| and all its
  8632.    * children and adds them all to |socketPoolList|.  |parent| is the
  8633.    * SocketPoolWrapper for the parent of |origPool|, or null, if it's
  8634.    * a top level socket pool.
  8635.    */
  8636.   function addSocketPoolsToList(socketPoolList, origPool, parent) {
  8637.     var socketPool = new SocketPoolWrapper(origPool, parent);
  8638.     socketPoolList.push(socketPool);
  8639.     if (origPool.nested_pools) {
  8640.       for (var i = 0; i < origPool.nested_pools.length; ++i) {
  8641.         addSocketPoolsToList(socketPoolList,
  8642.                               origPool.nested_pools[i],
  8643.                               socketPool);
  8644.       }
  8645.     }
  8646.   }
  8647.  
  8648.   /**
  8649.    * Returns a table printer containing information on each
  8650.    * SocketPoolWrapper in |socketPools|.
  8651.    */
  8652.   SocketPoolWrapper.createTablePrinter = function(socketPools) {
  8653.     var tablePrinter = new TablePrinter();
  8654.     tablePrinter.addHeaderCell('Name');
  8655.     tablePrinter.addHeaderCell('Handed Out');
  8656.     tablePrinter.addHeaderCell('Idle');
  8657.     tablePrinter.addHeaderCell('Connecting');
  8658.     tablePrinter.addHeaderCell('Max');
  8659.     tablePrinter.addHeaderCell('Max Per Group');
  8660.     tablePrinter.addHeaderCell('Generation');
  8661.  
  8662.     for (var i = 0; i < socketPools.length; i++) {
  8663.       var origPool = socketPools[i].origPool;
  8664.  
  8665.       tablePrinter.addRow();
  8666.       tablePrinter.addCell(socketPools[i].fullName);
  8667.  
  8668.       tablePrinter.addCell(origPool.handed_out_socket_count);
  8669.       var idleCell = tablePrinter.addCell(origPool.idle_socket_count);
  8670.       var connectingCell =
  8671.           tablePrinter.addCell(origPool.connecting_socket_count);
  8672.  
  8673.       if (origPool.groups) {
  8674.         var idleSources = [];
  8675.         var connectingSources = [];
  8676.         for (var groupName in origPool.groups) {
  8677.           var group = origPool.groups[groupName];
  8678.           idleSources = idleSources.concat(group.idle_sockets);
  8679.           connectingSources = connectingSources.concat(group.connect_jobs);
  8680.         }
  8681.         idleCell.link = sourceListLink(idleSources);
  8682.         connectingCell.link = sourceListLink(connectingSources);
  8683.       }
  8684.  
  8685.       tablePrinter.addCell(origPool.max_socket_count);
  8686.       tablePrinter.addCell(origPool.max_sockets_per_group);
  8687.       tablePrinter.addCell(origPool.pool_generation_number);
  8688.     }
  8689.     return tablePrinter;
  8690.   };
  8691.  
  8692.   SocketPoolWrapper.prototype = {
  8693.     /**
  8694.      * Returns a table printer containing information on all a
  8695.      * socket pool's groups.
  8696.      */
  8697.     createGroupTablePrinter: function() {
  8698.       var tablePrinter = new TablePrinter();
  8699.       tablePrinter.setTitle(this.fullName);
  8700.  
  8701.       tablePrinter.addHeaderCell('Name');
  8702.       tablePrinter.addHeaderCell('Pending');
  8703.       tablePrinter.addHeaderCell('Top Priority');
  8704.       tablePrinter.addHeaderCell('Active');
  8705.       tablePrinter.addHeaderCell('Idle');
  8706.       tablePrinter.addHeaderCell('Connect Jobs');
  8707.       tablePrinter.addHeaderCell('Backup Job');
  8708.       tablePrinter.addHeaderCell('Stalled');
  8709.  
  8710.       for (var groupName in this.origPool.groups) {
  8711.         var group = this.origPool.groups[groupName];
  8712.  
  8713.         tablePrinter.addRow();
  8714.         tablePrinter.addCell(groupName);
  8715.         tablePrinter.addCell(group.pending_request_count);
  8716.         if (group.top_pending_priority != undefined)
  8717.           tablePrinter.addCell(group.top_pending_priority);
  8718.         else
  8719.           tablePrinter.addCell('-');
  8720.  
  8721.         tablePrinter.addCell(group.active_socket_count);
  8722.         var idleCell = tablePrinter.addCell(group.idle_sockets.length);
  8723.         var connectingCell = tablePrinter.addCell(group.connect_jobs.length);
  8724.  
  8725.         idleCell.link = sourceListLink(group.idle_sockets);
  8726.         connectingCell.link = sourceListLink(group.connect_jobs);
  8727.  
  8728.         tablePrinter.addCell(group.has_backup_job);
  8729.         tablePrinter.addCell(group.is_stalled);
  8730.       }
  8731.       return tablePrinter;
  8732.     }
  8733.   };
  8734.  
  8735.   /**
  8736.    * Takes in a list of source IDs and returns a link that will select the
  8737.    * specified sources.
  8738.    */
  8739.   function sourceListLink(sources) {
  8740.     if (!sources.length)
  8741.       return null;
  8742.     return '#events&q=id:' + sources.join(',');
  8743.   }
  8744.  
  8745.   return SocketPoolWrapper;
  8746. })();
  8747.  
  8748. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  8749. // Use of this source code is governed by a BSD-style license that can be
  8750. // found in the LICENSE file.
  8751.  
  8752. /**
  8753.  * This view displays information on the state of all socket pools.
  8754.  *
  8755.  *   - Shows a summary of the state of each socket pool at the top.
  8756.  *   - For each pool with allocated sockets or connect jobs, shows all its
  8757.  *     groups with any allocated sockets.
  8758.  */
  8759. var SocketsView = (function() {
  8760.   'use strict';
  8761.  
  8762.   // We inherit from DivView.
  8763.   var superClass = DivView;
  8764.  
  8765.   /**
  8766.    * @constructor
  8767.    */
  8768.   function SocketsView() {
  8769.     assertFirstConstructorCall(SocketsView);
  8770.  
  8771.     // Call superclass's constructor.
  8772.     superClass.call(this, SocketsView.MAIN_BOX_ID);
  8773.  
  8774.     g_browser.addSocketPoolInfoObserver(this, true);
  8775.     this.socketPoolDiv_ = $(SocketsView.SOCKET_POOL_DIV_ID);
  8776.     this.socketPoolGroupsDiv_ = $(SocketsView.SOCKET_POOL_GROUPS_DIV_ID);
  8777.  
  8778.     var closeIdleButton = $(SocketsView.CLOSE_IDLE_SOCKETS_BUTTON_ID);
  8779.     closeIdleButton.onclick = this.closeIdleSockets.bind(this);
  8780.  
  8781.     var flushSocketsButton = $(SocketsView.SOCKET_POOL_FLUSH_BUTTON_ID);
  8782.     flushSocketsButton.onclick = this.flushSocketPools.bind(this);
  8783.   }
  8784.  
  8785.   // ID for special HTML element in category_tabs.html
  8786.   SocketsView.TAB_HANDLE_ID = 'tab-handle-sockets';
  8787.  
  8788.   // IDs for special HTML elements in sockets_view.html
  8789.   SocketsView.MAIN_BOX_ID = 'sockets-view-tab-content';
  8790.   SocketsView.SOCKET_POOL_DIV_ID = 'sockets-view-pool-div';
  8791.   SocketsView.SOCKET_POOL_GROUPS_DIV_ID = 'sockets-view-pool-groups-div';
  8792.   SocketsView.CLOSE_IDLE_SOCKETS_BUTTON_ID = 'sockets-view-close-idle-button';
  8793.   SocketsView.SOCKET_POOL_FLUSH_BUTTON_ID = 'sockets-view-flush-button';
  8794.  
  8795.   cr.addSingletonGetter(SocketsView);
  8796.  
  8797.   SocketsView.prototype = {
  8798.     // Inherit the superclass's methods.
  8799.     __proto__: superClass.prototype,
  8800.  
  8801.     onLoadLogFinish: function(data) {
  8802.       return this.onSocketPoolInfoChanged(data.socketPoolInfo);
  8803.     },
  8804.  
  8805.     onSocketPoolInfoChanged: function(socketPoolInfo) {
  8806.       this.socketPoolDiv_.innerHTML = '';
  8807.       this.socketPoolGroupsDiv_.innerHTML = '';
  8808.  
  8809.       if (!socketPoolInfo)
  8810.         return false;
  8811.  
  8812.       var socketPools = SocketPoolWrapper.createArrayFrom(socketPoolInfo);
  8813.       var tablePrinter = SocketPoolWrapper.createTablePrinter(socketPools);
  8814.       tablePrinter.toHTML(this.socketPoolDiv_, 'styled-table');
  8815.  
  8816.       // Add table for each socket pool with information on each of its groups.
  8817.       for (var i = 0; i < socketPools.length; ++i) {
  8818.         if (socketPools[i].origPool.groups != undefined) {
  8819.           var p = addNode(this.socketPoolGroupsDiv_, 'p');
  8820.           var br = addNode(p, 'br');
  8821.           var groupTablePrinter = socketPools[i].createGroupTablePrinter();
  8822.           groupTablePrinter.toHTML(p, 'styled-table');
  8823.         }
  8824.       }
  8825.       return true;
  8826.     },
  8827.  
  8828.     closeIdleSockets: function() {
  8829.       g_browser.sendCloseIdleSockets();
  8830.       g_browser.checkForUpdatedInfo(false);
  8831.     },
  8832.  
  8833.     flushSocketPools: function() {
  8834.       g_browser.sendFlushSocketPools();
  8835.       g_browser.checkForUpdatedInfo(false);
  8836.     }
  8837.   };
  8838.  
  8839.   return SocketsView;
  8840. })();
  8841.  
  8842. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  8843. // Use of this source code is governed by a BSD-style license that can be
  8844. // found in the LICENSE file.
  8845.  
  8846. /**
  8847.  * This view displays a summary of the state of each SPDY sessions, and
  8848.  * has links to display them in the events tab.
  8849.  */
  8850. var SpdyView = (function() {
  8851.   'use strict';
  8852.  
  8853.   // We inherit from DivView.
  8854.   var superClass = DivView;
  8855.  
  8856.   /**
  8857.    * @constructor
  8858.    */
  8859.   function SpdyView() {
  8860.     assertFirstConstructorCall(SpdyView);
  8861.  
  8862.     // Call superclass's constructor.
  8863.     superClass.call(this, SpdyView.MAIN_BOX_ID);
  8864.  
  8865.     g_browser.addSpdySessionInfoObserver(this, true);
  8866.     g_browser.addSpdyStatusObserver(this, true);
  8867.     g_browser.addSpdyAlternateProtocolMappingsObserver(this, true);
  8868.  
  8869.     this.spdyEnabledSpan_ = $(SpdyView.ENABLED_SPAN_ID);
  8870.     this.spdyUseAlternateProtocolSpan_ =
  8871.         $(SpdyView.USE_ALTERNATE_PROTOCOL_SPAN_ID);
  8872.     this.spdyForceAlwaysSpan_ = $(SpdyView.FORCE_ALWAYS_SPAN_ID);
  8873.     this.spdyForceOverSslSpan_ = $(SpdyView.FORCE_OVER_SSL_SPAN_ID);
  8874.     this.spdyNextProtocolsSpan_ = $(SpdyView.NEXT_PROTOCOLS_SPAN_ID);
  8875.  
  8876.     this.spdyAlternateProtocolMappingsDiv_ =
  8877.         $(SpdyView.ALTERNATE_PROTOCOL_MAPPINGS_DIV_ID);
  8878.     this.spdySessionNoneSpan_ = $(SpdyView.SESSION_NONE_SPAN_ID);
  8879.     this.spdySessionLinkSpan_ = $(SpdyView.SESSION_LINK_SPAN_ID);
  8880.     this.spdySessionDiv_ = $(SpdyView.SESSION_DIV_ID);
  8881.   }
  8882.  
  8883.   // ID for special HTML element in category_tabs.html
  8884.   SpdyView.TAB_HANDLE_ID = 'tab-handle-spdy';
  8885.  
  8886.   // IDs for special HTML elements in spdy_view.html
  8887.   SpdyView.MAIN_BOX_ID = 'spdy-view-tab-content';
  8888.   SpdyView.ENABLED_SPAN_ID = 'spdy-view-enabled-span';
  8889.   SpdyView.USE_ALTERNATE_PROTOCOL_SPAN_ID =
  8890.       'spdy-view-alternate-protocol-span';
  8891.   SpdyView.FORCE_ALWAYS_SPAN_ID = 'spdy-view-force-always-span';
  8892.   SpdyView.FORCE_OVER_SSL_SPAN_ID = 'spdy-view-force-over-ssl-span';
  8893.   SpdyView.NEXT_PROTOCOLS_SPAN_ID = 'spdy-view-next-protocols-span';
  8894.   SpdyView.ALTERNATE_PROTOCOL_MAPPINGS_DIV_ID =
  8895.       'spdy-view-alternate-protocol-mappings-div';
  8896.   SpdyView.SESSION_NONE_SPAN_ID = 'spdy-view-session-none-span';
  8897.   SpdyView.SESSION_LINK_SPAN_ID = 'spdy-view-session-link-span';
  8898.   SpdyView.SESSION_DIV_ID = 'spdy-view-session-div';
  8899.  
  8900.   cr.addSingletonGetter(SpdyView);
  8901.  
  8902.   SpdyView.prototype = {
  8903.     // Inherit the superclass's methods.
  8904.     __proto__: superClass.prototype,
  8905.  
  8906.     onLoadLogFinish: function(data) {
  8907.       return this.onSpdySessionInfoChanged(data.spdySessionInfo) &&
  8908.              this.onSpdyStatusChanged(data.spdyStatus) &&
  8909.              this.onSpdyAlternateProtocolMappingsChanged(
  8910.                  data.spdyAlternateProtocolMappings);
  8911.     },
  8912.  
  8913.     /**
  8914.      * If |spdySessionInfo| there are any sessions, display a single table with
  8915.      * information on each SPDY session.  Otherwise, displays "None".
  8916.      */
  8917.     onSpdySessionInfoChanged: function(spdySessionInfo) {
  8918.       this.spdySessionDiv_.innerHTML = '';
  8919.  
  8920.       var hasNoSession =
  8921.           (spdySessionInfo == null || spdySessionInfo.length == 0);
  8922.       setNodeDisplay(this.spdySessionNoneSpan_, hasNoSession);
  8923.       setNodeDisplay(this.spdySessionLinkSpan_, !hasNoSession);
  8924.  
  8925.       // Only want to be hide the tab if there's no data.  In the case of having
  8926.       // data but no sessions, still show the tab.
  8927.       if (!spdySessionInfo)
  8928.         return false;
  8929.  
  8930.       if (!hasNoSession) {
  8931.         var tablePrinter = createSessionTablePrinter(spdySessionInfo);
  8932.         tablePrinter.toHTML(this.spdySessionDiv_, 'styled-table');
  8933.       }
  8934.  
  8935.       return true;
  8936.     },
  8937.  
  8938.     /**
  8939.      * Displays information on the global SPDY status.
  8940.      */
  8941.     onSpdyStatusChanged: function(spdyStatus) {
  8942.       this.spdyEnabledSpan_.textContent = spdyStatus.spdy_enabled;
  8943.       this.spdyUseAlternateProtocolSpan_.textContent =
  8944.           spdyStatus.use_alternate_protocols;
  8945.       this.spdyForceAlwaysSpan_.textContent = spdyStatus.force_spdy_always;
  8946.       this.spdyForceOverSslSpan_.textContent = spdyStatus.force_spdy_over_ssl;
  8947.       this.spdyNextProtocolsSpan_.textContent = spdyStatus.next_protos;
  8948.  
  8949.       return true;
  8950.     },
  8951.  
  8952.     /**
  8953.      * If |spdyAlternateProtocolMappings| is not empty, displays a single table
  8954.      * with information on each alternate protocol enabled server.  Otherwise,
  8955.      * displays "None".
  8956.      */
  8957.     onSpdyAlternateProtocolMappingsChanged:
  8958.         function(spdyAlternateProtocolMappings) {
  8959.  
  8960.       this.spdyAlternateProtocolMappingsDiv_.innerHTML = '';
  8961.  
  8962.       if (spdyAlternateProtocolMappings != null &&
  8963.           spdyAlternateProtocolMappings.length > 0) {
  8964.         var tabPrinter = createAlternateProtocolMappingsTablePrinter(
  8965.                 spdyAlternateProtocolMappings);
  8966.         tabPrinter.toHTML(
  8967.             this.spdyAlternateProtocolMappingsDiv_, 'styled-table');
  8968.       } else {
  8969.         this.spdyAlternateProtocolMappingsDiv_.innerHTML = 'None';
  8970.       }
  8971.       return true;
  8972.     }
  8973.   };
  8974.  
  8975.   /**
  8976.    * Creates a table printer to print out the state of list of SPDY sessions.
  8977.    */
  8978.   function createSessionTablePrinter(spdySessions) {
  8979.     var tablePrinter = new TablePrinter();
  8980.     tablePrinter.addHeaderCell('Host');
  8981.     tablePrinter.addHeaderCell('Proxy');
  8982.     tablePrinter.addHeaderCell('ID');
  8983.     tablePrinter.addHeaderCell('Protocol Negotiatied');
  8984.     tablePrinter.addHeaderCell('Active streams');
  8985.     tablePrinter.addHeaderCell('Unclaimed pushed');
  8986.     tablePrinter.addHeaderCell('Max');
  8987.     tablePrinter.addHeaderCell('Initiated');
  8988.     tablePrinter.addHeaderCell('Pushed');
  8989.     tablePrinter.addHeaderCell('Pushed and claimed');
  8990.     tablePrinter.addHeaderCell('Abandoned');
  8991.     tablePrinter.addHeaderCell('Received frames');
  8992.     tablePrinter.addHeaderCell('Secure');
  8993.     tablePrinter.addHeaderCell('Sent settings');
  8994.     tablePrinter.addHeaderCell('Received settings');
  8995.     tablePrinter.addHeaderCell('Error');
  8996.  
  8997.     for (var i = 0; i < spdySessions.length; i++) {
  8998.       var session = spdySessions[i];
  8999.       tablePrinter.addRow();
  9000.  
  9001.       var host = session.host_port_pair;
  9002.       if (session.aliases)
  9003.         host += ' ' + session.aliases.join(' ');
  9004.       tablePrinter.addCell(host);
  9005.       tablePrinter.addCell(session.proxy);
  9006.  
  9007.       var idCell = tablePrinter.addCell(session.source_id);
  9008.       idCell.link = '#events&q=id:' + session.source_id;
  9009.  
  9010.       tablePrinter.addCell(session.protocol_negotiated);
  9011.       tablePrinter.addCell(session.active_streams);
  9012.       tablePrinter.addCell(session.unclaimed_pushed_streams);
  9013.       tablePrinter.addCell(session.max_concurrent_streams);
  9014.       tablePrinter.addCell(session.streams_initiated_count);
  9015.       tablePrinter.addCell(session.streams_pushed_count);
  9016.       tablePrinter.addCell(session.streams_pushed_and_claimed_count);
  9017.       tablePrinter.addCell(session.streams_abandoned_count);
  9018.       tablePrinter.addCell(session.frames_received);
  9019.       tablePrinter.addCell(session.is_secure);
  9020.       tablePrinter.addCell(session.sent_settings);
  9021.       tablePrinter.addCell(session.received_settings);
  9022.       tablePrinter.addCell(session.error);
  9023.     }
  9024.     return tablePrinter;
  9025.   }
  9026.  
  9027.   /**
  9028.    * Creates a table printer to print out the list of alternate protocol
  9029.    * mappings.
  9030.    */
  9031.   function createAlternateProtocolMappingsTablePrinter(
  9032.       spdyAlternateProtocolMappings) {
  9033.     var tablePrinter = new TablePrinter();
  9034.     tablePrinter.addHeaderCell('Host');
  9035.     tablePrinter.addHeaderCell('Alternate Protocol');
  9036.  
  9037.     for (var i = 0; i < spdyAlternateProtocolMappings.length; i++) {
  9038.       var entry = spdyAlternateProtocolMappings[i];
  9039.       tablePrinter.addRow();
  9040.  
  9041.       tablePrinter.addCell(entry.host_port_pair);
  9042.       tablePrinter.addCell(entry.alternate_protocol);
  9043.     }
  9044.     return tablePrinter;
  9045.   }
  9046.  
  9047.   return SpdyView;
  9048. })();
  9049.  
  9050. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  9051. // Use of this source code is governed by a BSD-style license that can be
  9052. // found in the LICENSE file.
  9053.  
  9054. /**
  9055.  * This view displays information on Winsock layered service providers and
  9056.  * namespace providers.
  9057.  *
  9058.  * For each layered service provider, shows the name, dll, and type
  9059.  * information.  For each namespace provider, shows the name and
  9060.  * whether or not it's active.
  9061.  */
  9062. var ServiceProvidersView = (function() {
  9063.   'use strict';
  9064.  
  9065.   // We inherit from DivView.
  9066.   var superClass = DivView;
  9067.  
  9068.   /**
  9069.    * @constructor
  9070.    */
  9071.   function ServiceProvidersView() {
  9072.     assertFirstConstructorCall(ServiceProvidersView);
  9073.  
  9074.     // Call superclass's constructor.
  9075.     superClass.call(this, ServiceProvidersView.MAIN_BOX_ID);
  9076.  
  9077.     this.serviceProvidersTbody_ =
  9078.         $(ServiceProvidersView.SERVICE_PROVIDERS_TBODY_ID);
  9079.     this.namespaceProvidersTbody_ =
  9080.         $(ServiceProvidersView.NAMESPACE_PROVIDERS_TBODY_ID);
  9081.  
  9082.     g_browser.addServiceProvidersObserver(this, true);
  9083.   }
  9084.  
  9085.   // ID for special HTML element in category_tabs.html
  9086.   ServiceProvidersView.TAB_HANDLE_ID = 'tab-handle-service-providers';
  9087.  
  9088.   // IDs for special HTML elements in service_providers_view.html
  9089.   ServiceProvidersView.MAIN_BOX_ID = 'service-providers-view-tab-content';
  9090.   ServiceProvidersView.SERVICE_PROVIDERS_TBODY_ID =
  9091.       'service-providers-view-tbody';
  9092.   ServiceProvidersView.NAMESPACE_PROVIDERS_TBODY_ID =
  9093.       'service-providers-view-namespace-providers-tbody';
  9094.  
  9095.   cr.addSingletonGetter(ServiceProvidersView);
  9096.  
  9097.   ServiceProvidersView.prototype = {
  9098.     // Inherit the superclass's methods.
  9099.     __proto__: superClass.prototype,
  9100.  
  9101.     onLoadLogFinish: function(data) {
  9102.       return this.onServiceProvidersChanged(data.serviceProviders);
  9103.     },
  9104.  
  9105.     onServiceProvidersChanged: function(serviceProviders) {
  9106.       return serviceProviders &&
  9107.           this.updateServiceProviders_(serviceProviders['service_providers']) &&
  9108.           this.updateNamespaceProviders_(
  9109.               serviceProviders['namespace_providers']);
  9110.     },
  9111.  
  9112.      /**
  9113.      * Updates the table of layered service providers.
  9114.      */
  9115.     updateServiceProviders_: function(serviceProviders) {
  9116.       this.serviceProvidersTbody_.innerHTML = '';
  9117.  
  9118.       if (!serviceProviders)
  9119.         return false;
  9120.  
  9121.       // Add a table row for each service provider.
  9122.       for (var i = 0; i < serviceProviders.length; ++i) {
  9123.         var tr = addNode(this.serviceProvidersTbody_, 'tr');
  9124.         var entry = serviceProviders[i];
  9125.  
  9126.         addNodeWithText(tr, 'td', entry.name);
  9127.         addNodeWithText(tr, 'td', entry.version);
  9128.         addNodeWithText(tr, 'td', getLayeredServiceProviderType(entry));
  9129.         addNodeWithText(tr, 'td', getSocketType(entry));
  9130.         addNodeWithText(tr, 'td', getProtocolType(entry));
  9131.         addNodeWithText(tr, 'td', entry.path);
  9132.       }
  9133.       return true;
  9134.     },
  9135.  
  9136.     /**
  9137.      * Updates the lable of namespace providers.
  9138.      */
  9139.     updateNamespaceProviders_: function(namespaceProviders) {
  9140.       this.namespaceProvidersTbody_.innerHTML = '';
  9141.  
  9142.       if (!namespaceProviders)
  9143.         return false;
  9144.  
  9145.       // Add a table row for each namespace provider.
  9146.       for (var i = 0; i < namespaceProviders.length; ++i) {
  9147.         var tr = addNode(this.namespaceProvidersTbody_, 'tr');
  9148.         var entry = namespaceProviders[i];
  9149.         addNodeWithText(tr, 'td', entry.name);
  9150.         addNodeWithText(tr, 'td', entry.version);
  9151.         addNodeWithText(tr, 'td', getNamespaceProviderType(entry));
  9152.         addNodeWithText(tr, 'td', entry.active);
  9153.       }
  9154.       return true;
  9155.     }
  9156.   };
  9157.  
  9158.   /**
  9159.    * Returns type of a layered service provider.
  9160.    */
  9161.   function getLayeredServiceProviderType(serviceProvider) {
  9162.     if (serviceProvider.chain_length == 0)
  9163.       return 'Layer';
  9164.     if (serviceProvider.chain_length == 1)
  9165.       return 'Base';
  9166.     return 'Chain';
  9167.   }
  9168.  
  9169.   var NAMESPACE_PROVIDER_PTYPE = {
  9170.     '12': 'NS_DNS',
  9171.     '15': 'NS_NLA',
  9172.     '16': 'NS_BTH',
  9173.     '32': 'NS_NTDS',
  9174.     '37': 'NS_EMAIL',
  9175.     '38': 'NS_PNRPNAME',
  9176.     '39': 'NS_PNRPCLOUD'
  9177.   };
  9178.  
  9179.   /**
  9180.    * Returns the type of a namespace provider as a string.
  9181.    */
  9182.   function getNamespaceProviderType(namespaceProvider) {
  9183.     return tryGetValueWithKey(NAMESPACE_PROVIDER_PTYPE,
  9184.                               namespaceProvider.type);
  9185.   };
  9186.  
  9187.   var SOCKET_TYPE = {
  9188.     '1': 'SOCK_STREAM',
  9189.     '2': 'SOCK_DGRAM',
  9190.     '3': 'SOCK_RAW',
  9191.     '4': 'SOCK_RDM',
  9192.     '5': 'SOCK_SEQPACKET'
  9193.   };
  9194.  
  9195.   /**
  9196.    * Returns socket type of a layered service provider as a string.
  9197.    */
  9198.   function getSocketType(layeredServiceProvider) {
  9199.     return tryGetValueWithKey(SOCKET_TYPE,
  9200.                               layeredServiceProvider.socket_type);
  9201.   }
  9202.  
  9203.   var PROTOCOL_TYPE = {
  9204.     '1': 'IPPROTO_ICMP',
  9205.     '6': 'IPPROTO_TCP',
  9206.     '17': 'IPPROTO_UDP',
  9207.     '58': 'IPPROTO_ICMPV6'
  9208.   };
  9209.  
  9210.   /**
  9211.    * Returns protocol type of a layered service provider as a string.
  9212.    */
  9213.   function getProtocolType(layeredServiceProvider) {
  9214.     return tryGetValueWithKey(PROTOCOL_TYPE,
  9215.                               layeredServiceProvider.socket_protocol);
  9216.   }
  9217.  
  9218.   return ServiceProvidersView;
  9219. })();
  9220.  
  9221. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  9222. // Use of this source code is governed by a BSD-style license that can be
  9223. // found in the LICENSE file.
  9224.  
  9225. /**
  9226.  * This view displays network related log data and is specific fo ChromeOS.
  9227.  * We get log data from chrome by filtering system logs for network related
  9228.  * keywords. Logs are not fetched until we actually need them.
  9229.  */
  9230. var LogsView = (function() {
  9231.   'use strict';
  9232.  
  9233.   // Special classes (defined in logs_view.css).
  9234.   var LOG_ROW_COLLAPSED_CLASSNAME = 'logs-view-log-row-collapsed';
  9235.   var LOG_ROW_EXPANDED_CLASSNAME = 'logs-view-log-row-expanded';
  9236.   var LOG_CELL_TEXT_CLASSNAME = 'logs-view-log-cell-text';
  9237.   var LOG_CELL_LOG_CLASSNAME = 'logs-view-log-cell-log';
  9238.   var LOG_TABLE_BUTTON_COLUMN_CLASSNAME = 'logs-view-log-table-button-column';
  9239.   var LOG_BUTTON_CLASSNAME = 'logs-view-log-button';
  9240.  
  9241.   // We inherit from DivView.
  9242.   var superClass = DivView;
  9243.  
  9244.   /**
  9245.    * @constructor
  9246.    */
  9247.   function LogsView() {
  9248.     assertFirstConstructorCall(LogsView);
  9249.  
  9250.     // Call superclass's constructor.
  9251.     superClass.call(this, LogsView.MAIN_BOX_ID);
  9252.  
  9253.     var tableDiv = $(LogsView.TABLE_ID);
  9254.     this.rows = [];
  9255.     this.populateTable(tableDiv, LOG_FILTER_LIST);
  9256.     $(LogsView.GLOBAL_SHOW_BUTTON_ID).addEventListener('click',
  9257.         this.onGlobalChangeVisibleClick_.bind(this, true));
  9258.     $(LogsView.GLOBAL_HIDE_BUTTON_ID).addEventListener('click',
  9259.         this.onGlobalChangeVisibleClick_.bind(this, false));
  9260.     $(LogsView.REFRESH_LOGS_BUTTON_ID).addEventListener('click',
  9261.         this.onLogsRefresh_.bind(this));
  9262.   }
  9263.  
  9264.   // ID for special HTML element in category_tabs.html
  9265.   LogsView.TAB_HANDLE_ID = 'tab-handle-logs';
  9266.  
  9267.   // IDs for special HTML elements in logs_view.html
  9268.   LogsView.MAIN_BOX_ID = 'logs-view-tab-content';
  9269.   LogsView.TABLE_ID = 'logs-view-log-table';
  9270.   LogsView.GLOBAL_SHOW_BUTTON_ID = 'logs-view-global-show-btn';
  9271.   LogsView.GLOBAL_HIDE_BUTTON_ID = 'logs-view-global-hide-btn';
  9272.   LogsView.REFRESH_LOGS_BUTTON_ID = 'logs-view-refresh-btn';
  9273.  
  9274.   cr.addSingletonGetter(LogsView);
  9275.  
  9276.   /**
  9277.    * Contains log keys we are interested in.
  9278.    */
  9279.   var LOG_FILTER_LIST = [
  9280.     {
  9281.       key: 'syslog',
  9282.     },
  9283.     {
  9284.       key: 'ui_log',
  9285.     },
  9286.     {
  9287.       key: 'chrome_system_log',
  9288.     },
  9289.     {
  9290.       key: 'chrome_log',
  9291.     }
  9292.   ];
  9293.  
  9294.   LogsView.prototype = {
  9295.     // Inherit the superclass's methods.
  9296.     __proto__: superClass.prototype,
  9297.  
  9298.     /**
  9299.      * Called during View's initialization. Creates the row of a table logs will
  9300.      * be shown in. Each row has 4 cells.
  9301.      *
  9302.      * First cell's content will be set to |logKey|, second will contain a
  9303.      * button that will be used to show or hide third cell, which will contain
  9304.      * the filtered log.
  9305.      * |logKey| also tells us which log we are getting data from.
  9306.      */
  9307.     createTableRow: function(logKey) {
  9308.       var row = document.createElement('tr');
  9309.  
  9310.       var cells = [];
  9311.       for (var i = 0; i < 3; i++) {
  9312.         var rowCell = document.createElement('td');
  9313.         cells.push(rowCell);
  9314.         row.appendChild(rowCell);
  9315.       }
  9316.       // Log key cell.
  9317.       cells[0].className = LOG_CELL_TEXT_CLASSNAME;
  9318.       cells[0].textContent = logKey;
  9319.       // Cell log is displayed in. Log content is in div element that is
  9320.       // initially hidden and empty.
  9321.       cells[2].className = LOG_CELL_TEXT_CLASSNAME;
  9322.       var logDiv = document.createElement('div');
  9323.       logDiv.textContent = '';
  9324.       logDiv.className = LOG_CELL_LOG_CLASSNAME;
  9325.       logDiv.id = 'logs-view.log-cell.' + this.rows.length;
  9326.       cells[2].appendChild(logDiv);
  9327.  
  9328.       // Button that we use to show or hide div element with log content. Logs
  9329.       // are not visible initially, so we initialize button accordingly.
  9330.       var expandButton = document.createElement('button');
  9331.       expandButton.textContent = 'Show...';
  9332.       expandButton.className = LOG_BUTTON_CLASSNAME;
  9333.       expandButton.addEventListener('click',
  9334.                                     this.onButtonClicked_.bind(this, row));
  9335.  
  9336.       // Cell that contains show/hide button.
  9337.       cells[1].appendChild(expandButton);
  9338.       cells[1].className = LOG_TABLE_BUTTON_COLUMN_CLASSNAME;
  9339.  
  9340.       // Initially, log is not visible.
  9341.       row.className = LOG_ROW_COLLAPSED_CLASSNAME;
  9342.  
  9343.       // We will need those to process row buttons' onclick events.
  9344.       row.logKey = logKey;
  9345.       row.expandButton = expandButton;
  9346.       row.logDiv = logDiv;
  9347.       row.logVisible = false;
  9348.       this.rows.push(row);
  9349.  
  9350.       return row;
  9351.     },
  9352.  
  9353.     /**
  9354.      * Initializes |tableDiv| to represent data from |logList| which should be
  9355.      * of type LOG_FILTER_LIST.
  9356.      */
  9357.     populateTable: function(tableDiv, logList) {
  9358.       for (var i = 0; i < logList.length; i++) {
  9359.         var logSource = this.createTableRow(logList[i].key);
  9360.         tableDiv.appendChild(logSource);
  9361.       }
  9362.     },
  9363.  
  9364.     /**
  9365.      * Processes clicks on buttons that show or hide log contents in log row.
  9366.      * Row containing the clicked button is given to the method since it
  9367.      * contains all data we need to process the click (unlike button object
  9368.      * itself).
  9369.      */
  9370.     onButtonClicked_: function(containingRow) {
  9371.       if (!containingRow.logVisible) {
  9372.         containingRow.className = LOG_ROW_EXPANDED_CLASSNAME;
  9373.         containingRow.expandButton.textContent = 'Hide...';
  9374.         var logDiv = containingRow.logDiv;
  9375.         if (logDiv.textContent == '') {
  9376.           logDiv.textContent = 'Getting logs...';
  9377.           // Callback will be executed by g_browser.
  9378.           g_browser.getSystemLog(containingRow.logKey,
  9379.                                  containingRow.logDiv.id);
  9380.         }
  9381.       } else {
  9382.         containingRow.className = LOG_ROW_COLLAPSED_CLASSNAME;
  9383.         containingRow.expandButton.textContent = 'Show...';
  9384.       }
  9385.       containingRow.logVisible = !containingRow.logVisible;
  9386.     },
  9387.  
  9388.     /**
  9389.      * Processes click on one of the buttons that are used to show or hide all
  9390.      * logs we care about.
  9391.      */
  9392.     onGlobalChangeVisibleClick_: function(isShowAll) {
  9393.       for (var row in this.rows) {
  9394.         if (isShowAll != this.rows[row].logVisible) {
  9395.           this.onButtonClicked_(this.rows[row]);
  9396.         }
  9397.       }
  9398.     },
  9399.  
  9400.     /**
  9401.      * Processes click event on the button we use to refresh fetched logs. We
  9402.      * get the newest logs from libcros, and refresh content of the visible log
  9403.      * cells.
  9404.      */
  9405.     onLogsRefresh_: function() {
  9406.       g_browser.refreshSystemLogs();
  9407.  
  9408.       var visibleLogRows = [];
  9409.       var hiddenLogRows = [];
  9410.       for (var row in this.rows) {
  9411.         if (this.rows[row].logVisible) {
  9412.           visibleLogRows.push(this.rows[row]);
  9413.         } else {
  9414.           hiddenLogRows.push(this.rows[row]);
  9415.         }
  9416.       }
  9417.  
  9418.       // We have to refresh text content in visible rows.
  9419.       for (row in visibleLogRows) {
  9420.         visibleLogRows[row].logDiv.textContent = 'Getting logs...';
  9421.         g_browser.getSystemLog(visibleLogRows[row].logKey,
  9422.                                visibleLogRows[row].logDiv.id);
  9423.       }
  9424.  
  9425.       // In hidden rows we just clear potential log text, so we know we have to
  9426.       // get new contents when we show the row next time.
  9427.       for (row in hiddenLogRows) {
  9428.         hiddenLogRows[row].logDiv.textContent = '';
  9429.       }
  9430.     }
  9431.   };
  9432.  
  9433.   return LogsView;
  9434. })();
  9435.  
  9436. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  9437. // Use of this source code is governed by a BSD-style license that can be
  9438. // found in the LICENSE file.
  9439.  
  9440. /**
  9441.  * This view displays information related to Prerendering.
  9442.  */
  9443. var PrerenderView = (function() {
  9444.   'use strict';
  9445.  
  9446.   // We inherit from DivView.
  9447.   var superClass = DivView;
  9448.  
  9449.   /**
  9450.    * @constructor
  9451.    */
  9452.   function PrerenderView() {
  9453.     assertFirstConstructorCall(PrerenderView);
  9454.  
  9455.     // Call superclass's constructor.
  9456.     superClass.call(this, PrerenderView.MAIN_BOX_ID);
  9457.  
  9458.     g_browser.addPrerenderInfoObserver(this, true);
  9459.     this.prerenderEnabledSpan_ = $(PrerenderView.ENABLED_SPAN_ID);
  9460.     this.prerenderOmniboxEnabledSpan_ =
  9461.         $(PrerenderView.OMNIBOX_ENABLED_SPAN_ID);
  9462.     this.prerenderHistoryDiv_ = $(PrerenderView.HISTORY_DIV_ID);
  9463.     this.prerenderActiveDiv_ = $(PrerenderView.ACTIVE_DIV_ID);
  9464.   }
  9465.  
  9466.   // ID for special HTML element in category_tabs.html
  9467.   PrerenderView.TAB_HANDLE_ID = 'tab-handle-prerender';
  9468.  
  9469.   // IDs for special HTML elements in prerender_view.html
  9470.   PrerenderView.MAIN_BOX_ID = 'prerender-view-tab-content';
  9471.   PrerenderView.ENABLED_SPAN_ID = 'prerender-view-enabled-span';
  9472.   PrerenderView.OMNIBOX_ENABLED_SPAN_ID = 'prerender-view-omnibox-enabled-span';
  9473.   PrerenderView.HISTORY_DIV_ID = 'prerender-view-history-div';
  9474.   PrerenderView.ACTIVE_DIV_ID = 'prerender-view-active-div';
  9475.  
  9476.   cr.addSingletonGetter(PrerenderView);
  9477.  
  9478.   PrerenderView.prototype = {
  9479.     // Inherit the superclass's methods.
  9480.     __proto__: superClass.prototype,
  9481.  
  9482.     onLoadLogFinish: function(data) {
  9483.       return this.onPrerenderInfoChanged(data.prerenderInfo);
  9484.     },
  9485.  
  9486.     onPrerenderInfoChanged: function(prerenderInfo) {
  9487.       this.prerenderEnabledSpan_.textContent = '';
  9488.       this.prerenderOmniboxEnabledSpan_.textContent = '';
  9489.       this.prerenderHistoryDiv_.innerHTML = '';
  9490.       this.prerenderActiveDiv_.innerHTML = '';
  9491.  
  9492.       if (prerenderInfo && ('enabled' in prerenderInfo)) {
  9493.         this.prerenderEnabledSpan_.textContent =
  9494.             prerenderInfo.enabled.toString();
  9495.         if (prerenderInfo.enabled_note) {
  9496.           this.prerenderEnabledSpan_.textContent +=
  9497.               ' ' + prerenderInfo.enabled_note;
  9498.         }
  9499.       }
  9500.  
  9501.       if (prerenderInfo && ('omnibox_enabled' in prerenderInfo)) {
  9502.         this.prerenderOmniboxEnabledSpan_.textContent =
  9503.             prerenderInfo.omnibox_enabled.toString();
  9504.       }
  9505.  
  9506.       if (!isValidPrerenderInfo(prerenderInfo))
  9507.         return false;
  9508.  
  9509.       var tabPrinter = createHistoryTablePrinter(prerenderInfo.history);
  9510.       tabPrinter.toHTML(this.prerenderHistoryDiv_, 'styled-table');
  9511.  
  9512.       var tabPrinter = createActiveTablePrinter(prerenderInfo.active);
  9513.       tabPrinter.toHTML(this.prerenderActiveDiv_, 'styled-table');
  9514.  
  9515.       return true;
  9516.     }
  9517.   };
  9518.  
  9519.   function isValidPrerenderInfo(prerenderInfo) {
  9520.     if (prerenderInfo == null) {
  9521.       return false;
  9522.     }
  9523.     if (!('history' in prerenderInfo) ||
  9524.         !('active' in prerenderInfo) ||
  9525.         !('enabled' in prerenderInfo)) {
  9526.       return false;
  9527.     }
  9528.     return true;
  9529.   }
  9530.  
  9531.   function createHistoryTablePrinter(prerenderHistory) {
  9532.     var tablePrinter = new TablePrinter();
  9533.     tablePrinter.addHeaderCell('Origin');
  9534.     tablePrinter.addHeaderCell('URL');
  9535.     tablePrinter.addHeaderCell('Final Status');
  9536.     tablePrinter.addHeaderCell('Time');
  9537.  
  9538.     for (var i = 0; i < prerenderHistory.length; i++) {
  9539.       var historyEntry = prerenderHistory[i];
  9540.       tablePrinter.addRow();
  9541.       tablePrinter.addCell(historyEntry.origin);
  9542.       tablePrinter.addCell(historyEntry.url);
  9543.       tablePrinter.addCell(historyEntry.final_status);
  9544.  
  9545.       var date = new Date(parseInt(historyEntry.end_time));
  9546.       // TODO(eroman): Switch to addNodeWithDate()
  9547.       tablePrinter.addCell(timeutil.dateToString(date));
  9548.     }
  9549.     return tablePrinter;
  9550.   }
  9551.  
  9552.   function createActiveTablePrinter(prerenderActive) {
  9553.     var tablePrinter = new TablePrinter();
  9554.     tablePrinter.addHeaderCell('URL');
  9555.     tablePrinter.addHeaderCell('Duration');
  9556.  
  9557.     for (var i = 0; i < prerenderActive.length; i++) {
  9558.       var activeEntry = prerenderActive[i];
  9559.       tablePrinter.addRow();
  9560.       tablePrinter.addCell(activeEntry.url);
  9561.       tablePrinter.addCell(activeEntry.duration);
  9562.     }
  9563.     return tablePrinter;
  9564.   }
  9565.  
  9566.   return PrerenderView;
  9567. })();
  9568.  
  9569. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  9570. // Use of this source code is governed by a BSD-style license that can be
  9571. // found in the LICENSE file.
  9572.  
  9573. /**
  9574.  * This view displays information on ChromeOS specific features.
  9575.  */
  9576. var CrosView = (function() {
  9577.   'use strict';
  9578.  
  9579.   var fileContent;
  9580.   var passcode = '';
  9581.  
  9582.   /**
  9583.    *  Clear file input div
  9584.    *
  9585.    *  @private
  9586.    */
  9587.   function clearFileInput_() {
  9588.     $(CrosView.IMPORT_DIV_ID).innerHTML = $(CrosView.IMPORT_DIV_ID).innerHTML;
  9589.     $(CrosView.IMPORT_ONC_ID).addEventListener('change',
  9590.                                                handleFileChangeEvent_,
  9591.                                                false);
  9592.   }
  9593.  
  9594.   /**
  9595.    *  Send file contents and passcode to C++ cros network library.
  9596.    *
  9597.    *  @private
  9598.    */
  9599.   function importONCFile_() {
  9600.     clearParseStatus_();
  9601.     if (fileContent)
  9602.       g_browser.importONCFile(fileContent, passcode);
  9603.     else
  9604.       setParseStatus_('ONC file parse failed: cannot read file');
  9605.     clearFileInput_();
  9606.   }
  9607.  
  9608.   /**
  9609.    *  Set the passcode var, and trigger onc import.
  9610.    *
  9611.    *  @param {string} value The passcode value.
  9612.    *  @private
  9613.    */
  9614.   function setPasscode_(value) {
  9615.     passcode = value;
  9616.     if (passcode)
  9617.       importONCFile_();
  9618.   }
  9619.  
  9620.   /**
  9621.    *  Unhide the passcode prompt input field and give it focus.
  9622.    *
  9623.    *  @private
  9624.    */
  9625.   function promptForPasscode_() {
  9626.     $(CrosView.PASSCODE_ID).hidden = false;
  9627.     $(CrosView.PASSCODE_INPUT_ID).focus();
  9628.     $(CrosView.PASSCODE_INPUT_ID).select();
  9629.   }
  9630.  
  9631.   /**
  9632.    *  Set the fileContent var, and trigger onc import if the file appears to
  9633.    *  not be encrypted, or prompt for passcode if the file is encrypted.
  9634.    *
  9635.    *  @private
  9636.    *  @param {string} text contents of selected file.
  9637.    */
  9638.   function setFileContent_(result) {
  9639.     fileContent = result;
  9640.     // Parse the JSON to get at the top level "Type" property.
  9641.     var json_object;
  9642.     // Ignore any parse errors: they'll get handled in the C++ import code.
  9643.     try {
  9644.       json_object = JSON.parse(fileContent);
  9645.     } catch (error) {}
  9646.     // Check if file is encrypted.
  9647.     if (json_object &&
  9648.         json_object.hasOwnProperty('Type') &&
  9649.         json_object.Type == 'EncryptedConfiguration') {
  9650.       promptForPasscode_();
  9651.     } else {
  9652.       importONCFile_();
  9653.     }
  9654.   }
  9655.  
  9656.   /**
  9657.    *  Clear ONC file parse status.  Clears and hides the parse status div.
  9658.    *
  9659.    *  @private
  9660.    */
  9661.   function clearParseStatus_(error) {
  9662.     var parseStatus = $(CrosView.PARSE_STATUS_ID);
  9663.     parseStatus.hidden = true;
  9664.     parseStatus.textContent = '';
  9665.   }
  9666.  
  9667.   /**
  9668.    *  Set ONC file parse status.
  9669.    *
  9670.    *  @private
  9671.    */
  9672.   function setParseStatus_(error) {
  9673.     var parseStatus = $(CrosView.PARSE_STATUS_ID);
  9674.     parseStatus.hidden = false;
  9675.     parseStatus.textContent = error ?
  9676.         'ONC file parse failed: ' + error : 'ONC file successfully parsed';
  9677.     reset_();
  9678.   }
  9679.  
  9680.   /**
  9681.    *  Set storing debug logs status.
  9682.    *
  9683.    *  @private
  9684.    */
  9685.   function setStoreDebugLogsStatus_(status) {
  9686.     $(CrosView.STORE_DEBUG_LOGS_STATUS_ID).innerText = status;
  9687.   }
  9688.  
  9689.   /**
  9690.    *  Set status for current debug mode.
  9691.    *
  9692.    *  @private
  9693.    */
  9694.   function setNetworkDebugModeStatus_(status) {
  9695.     $(CrosView.DEBUG_STATUS_ID).innerText = status;
  9696.   }
  9697.  
  9698.   /**
  9699.    *  An event listener for the file selection field.
  9700.    *
  9701.    *  @private
  9702.    */
  9703.   function handleFileChangeEvent_(event) {
  9704.     clearParseStatus_();
  9705.     var file = event.target.files[0];
  9706.     var reader = new FileReader();
  9707.     reader.onloadend = function(e) {
  9708.       setFileContent_(reader.result);
  9709.     };
  9710.     reader.readAsText(file);
  9711.   }
  9712.  
  9713.   /**
  9714.    *  Add event listeners for the file selection, passcode input
  9715.    *  fields, for the button for debug logs storing and for buttons
  9716.    *  for debug mode selection.
  9717.    *
  9718.    *  @private
  9719.    */
  9720.   function addEventListeners_() {
  9721.     $(CrosView.IMPORT_ONC_ID).addEventListener('change',
  9722.                                                handleFileChangeEvent_,
  9723.                                                false);
  9724.  
  9725.     $(CrosView.PASSCODE_INPUT_ID).addEventListener('change', function(event) {
  9726.       setPasscode_(this.value);
  9727.     }, false);
  9728.  
  9729.     $(CrosView.STORE_DEBUG_LOGS_ID).addEventListener('click', function(event) {
  9730.       $(CrosView.STORE_DEBUG_LOGS_STATUS_ID).innerText = '';
  9731.       g_browser.storeDebugLogs();
  9732.     }, false);
  9733.  
  9734.     $(CrosView.DEBUG_WIFI_ID).addEventListener('click', function(event) {
  9735.         setNetworkDebugMode_('wifi');
  9736.     }, false);
  9737.     $(CrosView.DEBUG_ETHERNET_ID).addEventListener('click', function(event) {
  9738.         setNetworkDebugMode_('ethernet');
  9739.     }, false);
  9740.     $(CrosView.DEBUG_CELLULAR_ID).addEventListener('click', function(event) {
  9741.         setNetworkDebugMode_('cellular');
  9742.     }, false);
  9743.     $(CrosView.DEBUG_WIMAX_ID).addEventListener('click', function(event) {
  9744.         setNetworkDebugMode_('wimax');
  9745.     }, false);
  9746.     $(CrosView.DEBUG_NONE_ID).addEventListener('click', function(event) {
  9747.         setNetworkDebugMode_('none');
  9748.     }, false);
  9749.   }
  9750.  
  9751.   /**
  9752.    *  Reset fileContent and passcode vars.
  9753.    *
  9754.    *  @private
  9755.    */
  9756.   function reset_() {
  9757.     fileContent = undefined;
  9758.     passcode = '';
  9759.     $(CrosView.PASSCODE_ID).hidden = true;
  9760.   }
  9761.  
  9762.   /**
  9763.    *  Enables or disables debug mode for a specified subsystem.
  9764.    *
  9765.    *  @private
  9766.    */
  9767.   function setNetworkDebugMode_(subsystem) {
  9768.     $(CrosView.DEBUG_STATUS_ID).innerText = '';
  9769.     g_browser.setNetworkDebugMode(subsystem);
  9770.   }
  9771.  
  9772.   /**
  9773.    *  @constructor
  9774.    *  @extends {DivView}
  9775.    */
  9776.   function CrosView() {
  9777.     assertFirstConstructorCall(CrosView);
  9778.  
  9779.     // Call superclass's constructor.
  9780.     DivView.call(this, CrosView.MAIN_BOX_ID);
  9781.  
  9782.     g_browser.addCrosONCFileParseObserver(this);
  9783.     g_browser.addStoreDebugLogsObserver(this);
  9784.     g_browser.addSetNetworkDebugModeObserver(this);
  9785.     addEventListeners_();
  9786.   }
  9787.  
  9788.   // ID for special HTML element in category_tabs.html
  9789.   CrosView.TAB_HANDLE_ID = 'tab-handle-chromeos';
  9790.  
  9791.   CrosView.MAIN_BOX_ID = 'chromeos-view-tab-content';
  9792.   CrosView.IMPORT_DIV_ID = 'chromeos-view-import-div';
  9793.   CrosView.IMPORT_ONC_ID = 'chromeos-view-import-onc';
  9794.   CrosView.PASSCODE_ID = 'chromeos-view-password-div';
  9795.   CrosView.PASSCODE_INPUT_ID = 'chromeos-view-onc-password';
  9796.   CrosView.PARSE_STATUS_ID = 'chromeos-view-parse-status';
  9797.   CrosView.STORE_DEBUG_LOGS_ID = 'chromeos-view-store-debug-logs';
  9798.   CrosView.STORE_DEBUG_LOGS_STATUS_ID = 'chromeos-view-store-debug-logs-status';
  9799.   CrosView.DEBUG_WIFI_ID = 'chromeos-view-network-debugging-wifi';
  9800.   CrosView.DEBUG_ETHERNET_ID = 'chromeos-view-network-debugging-ethernet';
  9801.   CrosView.DEBUG_CELLULAR_ID = 'chromeos-view-network-debugging-cellular';
  9802.   CrosView.DEBUG_WIMAX_ID = 'chromeos-view-network-debugging-wimax';
  9803.   CrosView.DEBUG_NONE_ID = 'chromeos-view-network-debugging-none';
  9804.   CrosView.DEBUG_STATUS_ID = 'chromeos-view-network-debugging-status';
  9805.  
  9806.   cr.addSingletonGetter(CrosView);
  9807.  
  9808.   CrosView.prototype = {
  9809.     // Inherit from DivView.
  9810.     __proto__: DivView.prototype,
  9811.  
  9812.     onONCFileParse: setParseStatus_,
  9813.     onStoreDebugLogs: setStoreDebugLogsStatus_,
  9814.     onSetNetworkDebugMode: setNetworkDebugModeStatus_,
  9815.   };
  9816.  
  9817.   return CrosView;
  9818. })();
  9819.  
  9820. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  9821. // Use of this source code is governed by a BSD-style license that can be
  9822. // found in the LICENSE file.
  9823.  
  9824. /**
  9825.  * This view displays a summary of the state of each HTTP pipelined connection,
  9826.  * and has links to display them in the events tab.
  9827.  */
  9828. var HttpPipelineView = (function() {
  9829.   'use strict';
  9830.  
  9831.   // We inherit from DivView.
  9832.   var superClass = DivView;
  9833.  
  9834.   /**
  9835.    * @constructor
  9836.    */
  9837.   function HttpPipelineView() {
  9838.     assertFirstConstructorCall(HttpPipelineView);
  9839.  
  9840.     // Call superclass's constructor.
  9841.     superClass.call(this, HttpPipelineView.MAIN_BOX_ID);
  9842.  
  9843.     g_browser.addHttpPipeliningStatusObserver(this, true);
  9844.  
  9845.     this.httpPipeliningEnabledSpan_ = $(HttpPipelineView.ENABLED_SPAN_ID);
  9846.     this.httpPipelineConnectionsNoneSpan_ =
  9847.         $(HttpPipelineView.CONNECTIONS_NONE_SPAN_ID);
  9848.     this.httpPipelineConnectionsLinkSpan_ =
  9849.         $(HttpPipelineView.CONNECTIONS_LINK_SPAN_ID);
  9850.     this.httpPipelineConnectionsDiv_ = $(HttpPipelineView.CONNECTIONS_DIV_ID);
  9851.     this.httpPipelineKnownHostsDiv_ = $(HttpPipelineView.KNOWN_HOSTS_DIV_ID);
  9852.   }
  9853.  
  9854.   // ID for special HTML element in category_tabs.html
  9855.   HttpPipelineView.TAB_HANDLE_ID = 'tab-handle-http-pipeline';
  9856.  
  9857.   // IDs for special HTML elements in http_pipeline_view.html
  9858.   HttpPipelineView.MAIN_BOX_ID = 'http-pipeline-view-tab-content';
  9859.   HttpPipelineView.ENABLED_SPAN_ID = 'http-pipeline-view-enabled-span';
  9860.   HttpPipelineView.CONNECTIONS_NONE_SPAN_ID =
  9861.       'http-pipeline-view-connections-none-span';
  9862.   HttpPipelineView.CONNECTIONS_LINK_SPAN_ID =
  9863.       'http-pipeline-view-connections-link-span';
  9864.   HttpPipelineView.CONNECTIONS_DIV_ID = 'http-pipeline-view-connections-div';
  9865.   HttpPipelineView.KNOWN_HOSTS_DIV_ID = 'http-pipeline-view-known-hosts-div';
  9866.  
  9867.   cr.addSingletonGetter(HttpPipelineView);
  9868.  
  9869.   HttpPipelineView.prototype = {
  9870.     // Inherit the superclass's methods.
  9871.     __proto__: superClass.prototype,
  9872.  
  9873.     onLoadLogFinish: function(data) {
  9874.       return this.onHttpPipeliningStatusChanged(data.httpPipeliningStatus);
  9875.     },
  9876.  
  9877.     /**
  9878.      * Displays information on the global HTTP pipelining status.
  9879.      */
  9880.     onHttpPipeliningStatusChanged: function(httpPipelineStatus) {
  9881.       return this.displayHttpPipeliningEnabled(httpPipelineStatus) &&
  9882.              this.displayHttpPipelinedConnectionInfo(
  9883.                  httpPipelineStatus.pipelined_connection_info) &&
  9884.              this.displayHttpPipeliningKnownHosts(
  9885.                  httpPipelineStatus.pipelined_host_info);
  9886.     },
  9887.  
  9888.     displayHttpPipeliningEnabled: function(httpPipelineStatus) {
  9889.       this.httpPipeliningEnabledSpan_.textContent =
  9890.           httpPipelineStatus.pipelining_enabled;
  9891.  
  9892.       return httpPipelineStatus.pipelining_enabled;
  9893.     },
  9894.  
  9895.     /**
  9896.      * If |httpPipelinedConnectionInfo| is not empty, then display information
  9897.      * on each HTTP pipelined connection.  Otherwise, displays "None".
  9898.      */
  9899.     displayHttpPipelinedConnectionInfo:
  9900.         function(httpPipelinedConnectionInfo) {
  9901.       this.httpPipelineConnectionsDiv_.innerHTML = '';
  9902.  
  9903.       var hasInfo = (httpPipelinedConnectionInfo != null &&
  9904.                      httpPipelinedConnectionInfo.length > 0);
  9905.       setNodeDisplay(this.httpPipelineConnectionsNoneSpan_, !hasInfo);
  9906.       setNodeDisplay(this.httpPipelineConnectionsLinkSpan_, hasInfo);
  9907.  
  9908.       if (hasInfo) {
  9909.         var tablePrinter = createConnectionTablePrinter(
  9910.             httpPipelinedConnectionInfo);
  9911.         tablePrinter.toHTML(this.httpPipelineConnectionsDiv_, 'styled-table');
  9912.       }
  9913.  
  9914.       return true;
  9915.     },
  9916.  
  9917.     /**
  9918.      * If |httpPipeliningKnownHosts| is not empty, displays a single table
  9919.      * with information on known pipelining hosts.  Otherwise, displays "None".
  9920.      */
  9921.     displayHttpPipeliningKnownHosts: function(httpPipeliningKnownHosts) {
  9922.       this.httpPipelineKnownHostsDiv_.innerHTML = '';
  9923.  
  9924.       if (httpPipeliningKnownHosts != null &&
  9925.           httpPipeliningKnownHosts.length > 0) {
  9926.         var tabPrinter = createKnownHostsTablePrinter(httpPipeliningKnownHosts);
  9927.         tabPrinter.toHTML(
  9928.             this.httpPipelineKnownHostsDiv_, 'styled-table');
  9929.       } else {
  9930.         this.httpPipelineKnownHostsDiv_.innerHTML = 'None';
  9931.       }
  9932.       return true;
  9933.     }
  9934.   };
  9935.  
  9936.   /**
  9937.    * Creates a table printer to print out the state of a list of HTTP pipelined
  9938.    * connections.
  9939.    */
  9940.   function createConnectionTablePrinter(httpPipelinedConnectionInfo) {
  9941.     var tablePrinter = new TablePrinter();
  9942.     tablePrinter.addHeaderCell('Host');
  9943.     tablePrinter.addHeaderCell('Forced');
  9944.     tablePrinter.addHeaderCell('Depth');
  9945.     tablePrinter.addHeaderCell('Capacity');
  9946.     tablePrinter.addHeaderCell('Usable');
  9947.     tablePrinter.addHeaderCell('Active');
  9948.     tablePrinter.addHeaderCell('ID');
  9949.  
  9950.     for (var i = 0; i < httpPipelinedConnectionInfo.length; i++) {
  9951.       var host = httpPipelinedConnectionInfo[i];
  9952.       for (var j = 0; j < host.length; j++) {
  9953.         var connection = host[j];
  9954.         tablePrinter.addRow();
  9955.  
  9956.         tablePrinter.addCell(connection.host);
  9957.         tablePrinter.addCell(
  9958.             connection.forced === undefined ? false : connection.forced);
  9959.         tablePrinter.addCell(connection.depth);
  9960.         tablePrinter.addCell(connection.capacity);
  9961.         tablePrinter.addCell(connection.usable);
  9962.         tablePrinter.addCell(connection.active);
  9963.  
  9964.         var idCell = tablePrinter.addCell(connection.source_id);
  9965.         idCell.link = '#events&q=id:' + connection.source_id;
  9966.       }
  9967.     }
  9968.     return tablePrinter;
  9969.   }
  9970.  
  9971.   /**
  9972.    * Creates a table printer to print out the list of known hosts and whether or
  9973.    * not they support pipelining.
  9974.    */
  9975.   function createKnownHostsTablePrinter(httpPipeliningKnownHosts) {
  9976.     var tablePrinter = new TablePrinter();
  9977.     tablePrinter.addHeaderCell('Host');
  9978.     tablePrinter.addHeaderCell('Pipelining Capalibility');
  9979.  
  9980.     for (var i = 0; i < httpPipeliningKnownHosts.length; i++) {
  9981.       var entry = httpPipeliningKnownHosts[i];
  9982.       tablePrinter.addRow();
  9983.  
  9984.       tablePrinter.addCell(entry.host);
  9985.       tablePrinter.addCell(entry.capability);
  9986.     }
  9987.     return tablePrinter;
  9988.   }
  9989.  
  9990.   return HttpPipelineView;
  9991. })();
  9992.  
  9993. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  9994. // Use of this source code is governed by a BSD-style license that can be
  9995. // found in the LICENSE file.
  9996.  
  9997. /** This view displays summary statistics on bandwidth usage. */
  9998. var BandwidthView = (function() {
  9999.   'use strict';
  10000.  
  10001.   // We inherit from DivView.
  10002.   var superClass = DivView;
  10003.  
  10004.   /**
  10005.    * @constructor
  10006.    */
  10007.   function BandwidthView() {
  10008.     assertFirstConstructorCall(BandwidthView);
  10009.  
  10010.     // Call superclass's constructor.
  10011.     superClass.call(this, BandwidthView.MAIN_BOX_ID);
  10012.  
  10013.     g_browser.addSessionNetworkStatsObserver(this, true);
  10014.     g_browser.addHistoricNetworkStatsObserver(this, true);
  10015.  
  10016.     this.bandwidthUsageTable_ = $(BandwidthView.BANDWIDTH_USAGE_TABLE);
  10017.     this.sessionNetworkStats_ = null;
  10018.     this.historicNetworkStats_ = null;
  10019.   }
  10020.  
  10021.   // ID for special HTML element in category_tabs.html
  10022.   BandwidthView.TAB_HANDLE_ID = 'tab-handle-bandwidth';
  10023.  
  10024.   // IDs for special HTML elements in bandwidth_view.html
  10025.   BandwidthView.MAIN_BOX_ID = 'bandwidth-view-tab-content';
  10026.   BandwidthView.BANDWIDTH_USAGE_TABLE = 'bandwidth-usage-table';
  10027.  
  10028.   cr.addSingletonGetter(BandwidthView);
  10029.  
  10030.   BandwidthView.prototype = {
  10031.     // Inherit the superclass's methods.
  10032.     __proto__: superClass.prototype,
  10033.  
  10034.     onLoadLogFinish: function(data) {
  10035.       return this.onSessionNetworkStatsChanged(data.sessionNetworkStats) &&
  10036.           this.onHistoricNetworkStatsChanged(data.historicNetworkStats);
  10037.     },
  10038.  
  10039.     /**
  10040.      * Retains information on bandwidth usage this session.
  10041.      */
  10042.     onSessionNetworkStatsChanged: function(sessionNetworkStats) {
  10043.       this.sessionNetworkStats_ = sessionNetworkStats;
  10044.       this.updateBandwidthUsageTable();
  10045.       return true;
  10046.     },
  10047.  
  10048.     /**
  10049.      * Displays information on bandwidth usage this session and over the
  10050.      * browser's lifetime.
  10051.      */
  10052.     onHistoricNetworkStatsChanged: function(historicNetworkStats) {
  10053.       this.historicNetworkStats_ = historicNetworkStats;
  10054.       this.updateBandwidthUsageTable();
  10055.       return true;
  10056.     },
  10057.  
  10058.     /**
  10059.      * Update the bandwidth usage table.
  10060.      */
  10061.     updateBandwidthUsageTable: function() {
  10062.       this.bandwidthUsageTable_.innerHTML = '';
  10063.       var tabPrinter = createBandwidthUsageTablePrinter(
  10064.           this.sessionNetworkStats_, this.historicNetworkStats_);
  10065.       tabPrinter.toHTML(this.bandwidthUsageTable_, 'styled-table');
  10066.       return true;
  10067.     }
  10068.   };
  10069.  
  10070.   /**
  10071.    * Converts bytes to kilobytes rounded to one decimal place.
  10072.    */
  10073.   function bytesToRoundedKilobytes(val) {
  10074.     return (val / 1024).toFixed(1);
  10075.   }
  10076.  
  10077.   /**
  10078.    * Returns bandwidth savings as a percent rounded to one decimal place.
  10079.    */
  10080.   function getPercentSavings(original, received) {
  10081.     if (original > 0) {
  10082.       return ((original - received) * 100 / original).toFixed(1);
  10083.     }
  10084.     return '0.0';
  10085.   }
  10086.  
  10087.   /**
  10088.    * Adds a row of bandwidth usage statistics to the bandwidth usage table.
  10089.    */
  10090.   function addRow(tablePrinter, title, sessionValue, historicValue) {
  10091.     tablePrinter.addRow();
  10092.     tablePrinter.addCell(title);
  10093.     tablePrinter.addCell(sessionValue);
  10094.     tablePrinter.addCell(historicValue);
  10095.   }
  10096.  
  10097.   /**
  10098.    * Creates a table printer to print out the bandwidth usage statistics.
  10099.    */
  10100.   function createBandwidthUsageTablePrinter(sessionNetworkStats,
  10101.                                             historicNetworkStats) {
  10102.     var tablePrinter = new TablePrinter();
  10103.     tablePrinter.addHeaderCell('');
  10104.     tablePrinter.addHeaderCell('Session');
  10105.     tablePrinter.addHeaderCell('Total');
  10106.  
  10107.     var sessionOriginal = 0;
  10108.     var historicOriginal = 0;
  10109.     var sessionReceived = 0;
  10110.     var historicReceived = 0;
  10111.  
  10112.     if (sessionNetworkStats != null) {
  10113.       sessionOriginal = sessionNetworkStats.session_original_content_length;
  10114.       sessionReceived = sessionNetworkStats.session_received_content_length;
  10115.     }
  10116.     if (historicNetworkStats != null) {
  10117.       historicOriginal = historicNetworkStats.historic_original_content_length;
  10118.       historicReceived = historicNetworkStats.historic_received_content_length;
  10119.     }
  10120.  
  10121.     addRow(tablePrinter, 'Original (KB)',
  10122.         bytesToRoundedKilobytes(sessionOriginal),
  10123.         bytesToRoundedKilobytes(historicOriginal));
  10124.     addRow(tablePrinter, 'Received (KB)',
  10125.         bytesToRoundedKilobytes(sessionReceived),
  10126.         bytesToRoundedKilobytes(historicReceived));
  10127.     addRow(tablePrinter, 'Savings (KB)',
  10128.         bytesToRoundedKilobytes(sessionOriginal - sessionReceived),
  10129.         bytesToRoundedKilobytes(historicOriginal - historicReceived));
  10130.     addRow(tablePrinter, 'Savings (%)',
  10131.         getPercentSavings(sessionOriginal, sessionReceived),
  10132.         getPercentSavings(historicOriginal, historicReceived));
  10133.     return tablePrinter;
  10134.   }
  10135.  
  10136.   return BandwidthView;
  10137. })();
  10138.  
  10139.  
  10140. document.addEventListener('DOMContentLoaded', function() {
  10141.   MainView.getInstance();  // from main.js
  10142. });
  10143.